I am trying to determine which version of a member function gets called based on the class template parameter. I have tried this:
#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();
}
which I thought is saying "use the first MyFunction if T is int, and use the second MyFunction if T is not int, but I get compiler errors saying "error: no type named ‘type’ in ‘struct std::enable_if’". Can anyone point out what I am doing wrong here?
enable_if
works because the substitution of a template argument resulted in an error, and so that substitution is dropped from the overload resolution set and only other viable overloads are considered by the compiler.
In your example, there is no substitution occurring when instantiating the member functions because the template argument T
is already known at that time. The simplest way to achieve what you're attempting is to create a dummy template argument that is defaulted to T
and use that to perform 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;
}
};
Edit:
As HostileFork mentions in the comments, the original example leaves the possibility of the user explicitly specifying template arguments for the member functions and getting an incorrect result. The following should prevent explicit specializations of the member functions from compiling.
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;
}
};
A simple solution is to use delegation to worker private functions:
template<typename T>
struct Point
{
void MyFunction()
{
worker(static_cast<T*>(0)); //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;
}
};
When T
is int
, the first worker
function will be called, because static_cast<T*>(0)
turns out to be of type int*
. In all other cases, the template version of worker will be called.
enable_if
only works for deduced function template arguments or for specialized class template arguments. What you're doing doesn't work, because obviously with a fixed T = int
, the second declaration is just erroneous.
This is how it can be done:
template <typename T>
void MyFreeFunction(Point<T> const & p,
typename std::enable_if<std::is_same<T, int>::value>::type * = NULL)
{
std::cout << "T is int" << std::endl;
}
// etc.
int main()
{
Point<int> ip;
MyFreeFunction(ip);
}
An alternative would be to specialize Point
for various types T
, or to put the above free function into a nested member template wrapper (which is probably the more "proper" solution).
Based on Praetorian's suggestion (but without changing the return type of the function), this seems to work:
#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();
}
Point template bellow is possible to instantiate only with int or float as template argument T.
To answer the question: here worker() gets called exactly depending on the template parameter of the method() call, but still you are in control of the types.
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
}
};
Above worker() will of course work even if it is declared as static. For some valid reason. Few other extensions to the above are possible (and simple) but let us stick to the answer.
来源:https://stackoverflow.com/questions/13401716/selecting-a-member-function-using-different-enable-if-conditions