こんなことしたい
template <typename T>
void foo(const T& t) {
IF bar(t) would compile
bar(t);
ELSE
baz(t);
}
ここでは、何かを使って2つenable_if
に分割することでうまくいくと思いfoo
ましたが、詳細がわからないようです。これを達成する最も簡単な方法は何ですか?
name に対して行われる 2 つのルックアップがありますbar
。1 つは、 の定義コンテキストでの非修飾ルックアップですfoo
。もう 1 つは、各インスタンス化コンテキストでの引数依存のルックアップです (ただし、各インスタンス化コンテキストでのルックアップの結果は、2 つの異なるインスタンス化コンテキスト間で動作を変更することはできません)。
目的の動作を得るには、fallback
一意の型を返す名前空間にフォールバック関数を定義します。
namespace fallback {
// sizeof > 1
struct flag { char c[2]; };
flag bar(...);
}
省略記号の変換コストが最悪であるため、他に一致するものがない場合、bar
関数が呼び出されます。ここで、 の using ディレクティブによってその候補を関数に含めます。fallback
これfallback::bar
により、 の呼び出しに候補として含まれbar
ます。
ここで、 への呼び出しがbar
関数に解決されるかどうかを確認するために、それを呼び出して、戻り値の型が であるかどうかを確認しますflag
。それ以外の場合に選択された関数の戻り値の型は void になる可能性があるため、これを回避するにはカンマ演算子のトリックを実行する必要があります。
namespace fallback {
int operator,(flag, flag);
// map everything else to void
template<typename T>
void operator,(flag, T const&);
// sizeof 1
char operator,(int, flag);
}
関数が選択された場合、コンマ演算子の呼び出しは への参照を返しますint
。そうでない場合、または選択した関数が を返したvoid
場合は、呼び出しが返さvoid
れます。次に、flag
2 番目の引数として次の呼び出しを行うと、フォールバックが選択された場合は sizeof 1 の型が返され、他のvoid
ものが選択された場合は sizeof 1 が返されます ( が混在しているため、組み込みのコンマ演算子が使用されます)。
sizeof とデリゲートを構造体と比較します。
template<bool>
struct foo_impl;
/* bar available */
template<>
struct foo_impl<true> {
template<typename T>
static void foo(T const &t) {
bar(t);
}
};
/* bar not available */
template<>
struct foo_impl<false> {
template<typename T>
static void foo(T const&) {
std::cout << "not available, calling baz...";
}
};
template <typename T>
void foo(const T& t) {
using namespace fallback;
foo_impl<sizeof (fallback::flag(), bar(t), fallback::flag()) != 1>
::foo(t);
}
既存の関数にも省略記号がある場合、このソリューションはあいまいです。しかし、それはかなり可能性が低いようです。フォールバックを使用してテストします。
struct C { };
int main() {
// => "not available, calling baz..."
foo(C());
}
そして、引数依存ルックアップを使用して候補が見つかった場合
struct C { };
void bar(C) {
std::cout << "called!";
}
int main() {
// => "called!"
foo(C());
}
定義コンテキストでの非修飾ルックアップをテストするには、上記の次の関数を定義しましょう (上記foo_impl
のfoo
foo_impl テンプレートfoo
を配置して、両方が同じ定義コンテキストを持つようにします)。
void bar(double d) {
std::cout << "bar(double) called!";
}
// ... foo template ...
int main() {
// => "bar(double) called!"
foo(12);
}
litb はとても良い答えをくれました。しかし、より多くの文脈が与えられた場合、一般的でなく、精巧ではないものを思いつくことができなかったのではないでしょうか?
たとえば、どのようなタイプがありますT
か? なんでも?数種類?あなたがコントロールできる非常に制限されたセットですか?関数と組み合わせて設計するいくつかのクラスfoo
? 後者を考えると、次のようなものを簡単に置くことができます
typedef boolean<true> has_bar_func;
foo
タイプに変換し、それに基づいて別のオーバーロードに切り替えます。
template <typename T>
void foo_impl(const T& t, boolean<true> /*has_bar_func*/);
template <typename T>
void foo_impl(const T& t, boolean<false> /*has_bar_func*/);
template <typename T>
void foo(const T& t) {
foo_impl( t, typename T::has_bar_func() );
}
また、bar
/baz
関数はほぼすべての署名を持つことができますか、ある程度制限されたセットがありますか、それとも有効な署名は 1 つだけですか? 後者の場合、litb の (優れた) フォールバックのアイデアは、メタ関数を使用することと組み合わせてsizeof
、少し単純になる可能性があります。しかし、これは私が調査したわけではないので、単なる考えです。
litb のソリューションは機能すると思いますが、複雑すぎます。その理由は、彼がfallback::bar(...)
「最後の手段の機能」として機能する機能を導入し、それを呼び出さないために多大な努力を払っているためです。なんで?それには完璧な振る舞いがあるようです:
namespace fallback {
template<typename T>
inline void bar(T const& t, ...)
{
baz(t);
}
}
template<typename T>
void foo(T const& t)
{
using namespace fallback;
bar(t);
}
しかし、litb の元の投稿へのコメントで指摘したように、コンパイルに失敗する理由は多数あり、bar(t)
このソリューションが同じケースを処理するかどうかはわかりません。それは確かに失敗しますprivate bar::bar(T t)
//default
//////////////////////////////////////////
template <class T>
void foo(const T& t){
baz(t);
}
//specializations
//////////////////////////////////////////
template <>
void foo(const specialization_1& t){
bar(t);
}
....
template <>
void foo(const specialization_n& t){
bar(t);
}
Visual C ++に制限する場合は、__ if_existsステートメントと__if_not_existsステートメントを使用できます。
ピンチで便利ですが、プラットフォーム固有です。
ここで完全な特殊化(またはオーバーロード)をfooで使用することはできませんか?関数テンプレートの呼び出しバーがあると言いますが、特定のタイプでは、bazを呼び出すために完全に特化していますか?