クラステンプレートパラメータに基づいて、メンバー関数のどのバージョンが呼び出されるかを判断しようとしています。私はこれを試しました:
#include <iostream>
#include <type_traits>
template<typename T>
struct Point
{
void MyFunction(typename std::enable_if<std::is_same<T, int>::value, T >::type* = 0)
{
std::cout << "T is int." << std::endl;
}
void MyFunction(typename std::enable_if<!std::is_same<T, int>::value, float >::type* = 0)
{
std::cout << "T is not int." << std::endl;
}
};
int main()
{
Point<int> intPoint;
intPoint.MyFunction();
Point<float> floatPoint;
floatPoint.MyFunction();
}
これは、「Tがintの場合は最初のMyFunctionを使用し、Tがintでない場合は2番目のMyFunctionを使用する」と思っていましたが、「エラー:「structstd :: enable_if」に「type」という名前の型がありません」というコンパイラエラーが発生します。 。私がここで間違っていることを誰かが指摘できますか?
enable_if
が機能するのは、 テンプレート引数の置換によってエラーが発生した であり、そのため、置換はオーバーロード解決セットから削除され、他の実行可能なオーバーロードのみがコンパイラによって考慮されます。
あなたの例では、テンプレート引数T
はその時点ですでにわかっているため、メンバー関数をインスタンス化するときに置換は発生しません。試行していることを実現する最も簡単な方法は、デフォルトでT
に設定されているダミーのテンプレート引数を作成し、それを使用してSFINAEを実行することです。
template<typename T>
struct Point
{
template<typename U = T>
typename std::enable_if<std::is_same<U, int>::value>::type
MyFunction()
{
std::cout << "T is int." << std::endl;
}
template<typename U = T>
typename std::enable_if<std::is_same<U, float>::value>::type
MyFunction()
{
std::cout << "T is not int." << std::endl;
}
};
編集:
HostileForkがコメントで言及しているように、元の例では、ユーザーがメンバー関数のテンプレート引数を明示的に指定して、誤った結果を取得する可能性があります。以下は、メンバー関数の明示的な特殊化がコンパイルされないようにする必要があります。
template<typename T>
struct Point
{
template<typename... Dummy, typename U = T>
typename std::enable_if<std::is_same<U, int>::value>::type
MyFunction()
{
static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
std::cout << "T is int." << std::endl;
}
template<typename... Dummy, typename U = T>
typename std::enable_if<std::is_same<U, float>::value>::type
MyFunction()
{
static_assert(sizeof...(Dummy)==0, "Do not specify template arguments!");
std::cout << "T is not int." << std::endl;
}
};
簡単な解決策は、ワーカーへの委任を使用することですprivate関数:
_template<typename T>
struct Point
{
void MyFunction()
{
worker(static_cast<T*>(nullptr)); //pass null argument of type T*
}
private:
void worker(int*)
{
std::cout << "T is int." << std::endl;
}
template<typename U>
void worker(U*)
{
std::cout << "T is not int." << std::endl;
}
};
_
T
がint
の場合、static_cast<T*>(0)
のタイプが_int*
_であることが判明したため、最初のworker
関数が呼び出されます。それ以外の場合はすべて、workerのテンプレートバージョンが呼び出されます。
Praetorianの提案に基づくと(ただし、関数の戻り値の型を変更せずに)、これは機能するようです。
#include <iostream>
#include <type_traits>
template<typename T>
struct Point
{
template<typename U = T>
void MyFunction(typename std::enable_if<std::is_same<U, int>::value, U >::type* = 0)
{
std::cout << "T is int." << std::endl;
}
template<typename U = T>
void MyFunction(typename std::enable_if<!std::is_same<U, int>::value, float >::type* = 0)
{
std::cout << "T is not int." << std::endl;
}
};
int main()
{
Point<int> intPoint;
intPoint.MyFunction();
Point<float> floatPoint;
floatPoint.MyFunction();
}
enable_if
は推定関数テンプレート引数または特殊なクラステンプレート引数に対してのみ機能します。明らかに固定されたT = int
を使用すると、2番目の宣言が誤っているため、実行していることは機能しません。
これはそれができる方法です:
template <typename T>
void MyFreeFunction(Point<T> const & p,
typename std::enable_if<std::is_same<T, int>::value>::type * = nullptr)
{
std::cout << "T is int" << std::endl;
}
// etc.
int main()
{
Point<int> ip;
MyFreeFunction(ip);
}
別の方法は、Point
をさまざまなタイプのT
に特化するか、上記の無料の関数をネストされたメンバーテンプレートラッパーに配置することです(これはおそらくより「適切な」ソリューションです)。
以下のポイントテンプレートは、テンプレート引数Tとしてintまたはfloatを使用してのみインスタンス化できます。
質問に答えるには:ここでworker()は、method()呼び出しのテンプレートパラメーターに正確に応じて呼び出されますが、それでもタイプを制御できます。
template<typename T>
struct Point
{
static_assert (
std::is_same<T, int>() ||
std::is_same<T, float>()
);
template<typename U>
void method(U x_, U y_)
{
if constexpr (std::is_same<T, U>()) {
worker(x_, y_);
return;
}
// else
worker(
static_cast<T>(x_),
static_cast<T>(y_)
);
return ;
}
private:
mutable T x{}, y{};
void worker(T x_, T y_)
{
// nothing but T x, T y
}
};
上記のworker()は、静的として宣言されている場合でももちろん機能します。いくつかの正当な理由があります。上記の他のいくつかの拡張が可能(そして単純)ですが、答えに固執しましょう。