5

わかりました、これは少し複雑ですので、ご容赦ください。:)

この単純なクラス階層があります。

class A {};
class DA : public A {};
class DDA : public DA {};

そして、これらのクラスで動作する次の関数があります。

void f(A x) {
  std::cout << "f A" << std::endl;
}
void f(DA x) {
  std::cout << "f DA" << std::endl;
}
void f(DDA x) {
  std::cout << "f DDA" << std::endl;
}

次に、DA を少し異なる方法で処理する別の関数を追加します。

(1) 最初の試行は次のようになります。

void g(A t) {
  std::cout << "generic treatment of A" << std::endl;
  std::cout << "called from g: ";
  f(t);
}
void g(DA t) {
  std::cout << "special treatment of DA" << std::endl;
  std::cout << "called from g: ";
  f(t);
}

しかし、これを各クラスのオブジェクトで呼び出しても、明らかに望ましい効果はありません。

電話:

  A a; DA b; DDA c;
  g(a); g(b); g(c)

結果:

generic treatment of A
called from g: f A
special treatment of DA
called from g: f DA
special treatment of DA
called from g: f DA        //PROBLEM: g forgot that this DA was actually a DDA

(2) 代わりに、テンプレートを使用してみてください。

template<typename T>
void h(T t) {
  std::cout << "generic treatment of A" << std::endl;
  std::cout << "called from h: ";
  f(t);
}

template<>
void h<>(DA t) {
  std::cout << "special treatment of DA" << std::endl;
  std::cout << "called from h: ";
  f(t);
}

その結果:

generic treatment of A
called from h: f A
special treatment of DA
called from h: f DA
generic treatment of A    //PROBLEM: template specialization is not used
called from h: f DDA

では、テンプレートの特殊化を使用せず、特殊な場合に非テンプレート関数を定義するのはどうですか? (非常に紛らわしい問題に関する記事。)記事によると「第一級市民」である非テンプレート関数は、それを使用するには型変換が必要であるため、失われるように見えるため、まったく同じように動作することがわかりました。 . そして、それが使用された場合、最初のソリューションに戻るだけで (私は推測します)、DDA のタイプを忘れてしまいます。

(3) 今、私は仕事でこのコードに出くわしました。

template<typename T>
void i(T t, void* magic) {
  std::cout << "generic treatment of A" << std::endl;
  std::cout << "called from i: ";
  f(t);
}

template<typename T>
void i(T t, DA* magic) {
  std::cout << "special treatment of DA" << std::endl;
  std::cout << "called from i: ";
  f(t);
}

しかし、それは私が望むことを正確に行うようです:

generic treatment of A
called from i: f A
special treatment of DA
called from i: f DA
special treatment of DA
called from i: f DDA

奇妙な方法で呼び出す必要がありますが、 i(a, &a); です。i(b, &b); i(c,&c);

今、私はいくつかの質問があります:

  1. なぜこれが機能するのですか?
  2. それは良い考えだと思いますか?考えられる落とし穴はどこですか?
  3. この種の専門化を行う他の方法として、どのような提案がありますか?
  4. (型変換は、テンプレートの半順序などの狂気にどのように適合しますか...)

これがかなり明確だったことを願っています。:)

4

1 に答える 1

4

関数テンプレートのオーバーロード

template<typename T>
void i(T t, DA* magic) {

magicパラメータが type に変換可能な場合にのみ使用できますDA *。これは明らかに当てはまります&b&c、派生へのポインターはベースへのポインターに変換可能であるためです。void *関数テンプレートのオーバーロードは常に利用可能ですが、§13.3.3.2:4 に従ってDA *よりも優先されます。void *

13.3.3.2 暗黙の変換シーケンスのランキング [over.ics.rank]

[...]

4標準のコンバージョン シーケンスはランク順に並べられています。完全一致はプロモーションよりも優れたコンバージョンであり、プロモーションはコンバージョンよりも優れたコンバージョンです。次の規則のいずれかが適用されない限り、同じランクの 2 つの変換シーケンスは区別できません。

[...]

— classBが class から直接的または間接的に派生する場合A、 への変換はB*A*の変換よりも優れており、 への変換はB*への変換よりも優れています。void*A*void*B*void*

お気づきのとおり、これは完全に実行可能なスキームです。iで呼び出しを処理する別のテンプレート関数で魔法をラップする方が理にかなっています(a, &a)

template<typename T>
void j(T t) {
    i(t, &t);
}

安全面では問題ありません。DA *オーバーロードが失われた場合、オーバーロードはvoid *静かに選択されます。これが望ましいかどうかは、あなた次第です。

std::enable_if別の方法として、テンプレートを選択するために使用できます。

template<typename T>
typename std::enable_if<!std::is_base_of<DA, T>::value>::type g(T t) {
  std::cout << "generic treatment of A" << std::endl;
  f(t);
}
template<typename T>
typename std::enable_if<std::is_base_of<DA, T>::value>::type g(T t) {
  std::cout << "special treatment of DA" << std::endl;
  f(t);
}
于 2012-07-25T13:40:07.390 に答える