Selecting a member function using different enable_if conditions
Solution 1
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;
}
};
Solution 2
A simple solution is to use delegation to worker private functions:
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;
}
};
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.
Solution 3
I think this follows @Praetorian's solution, but I find it easier:
template<typename T>
struct Point
{
template<typename U = T>
std::enable_if_t<std::is_same<U, T>::value && std::is_same<T, int>::value>
MyFunction()
{
std::cout << "T is int." << std::endl;
}
template<typename U = T>
std::enable_if_t<std::is_same<U, T>::value && std::is_same<T, float>::value>
MyFunction()
{
std::cout << "T is not int." << std::endl;
}
};
Solution 4
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 * = nullptr)
{
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).
Solution 5
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();
}
David Doria
I live at the intersection of computer vision research and software engineering. With my background in image processing, computer vision, machine learning, and LiDAR data analysis, I am in a position to develop algorithmic solutions to these difficult problems. But these algorithms are not particularly beneficial as only ideas or prototypes - efficient and carefully executed implementations are the key to their real world application. I am passionate about the reusability of the software I develop - I have found that continually building tools for my team rather than simply solving the particular problem at hand is the key to accelerating future work and success.
Updated on September 20, 2020Comments
-
David Doria over 3 years
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?
-
HostileFork says dont trust SE over 11 yearsRelated Q&A: "What happened to my SFINAE" (redux)
-
aschepler about 5 yearsUpdate: C++20 will allow
template<typename T> struct Point { void MyFunction() requires (std::is_same_v<T, int>); void MyFunction() requires (!std::is_same_v<T, int>); };
. Here it's okay that the constraint expression is non-dependent and false - that just makes the entire function less preferred during overload resolution.
-
-
Nawaz over 11 yearsIn C++11, SFINAE rules has been modified a little bit, due to which SFINAE will not trigger on return type. In short, this answer is wrong.
-
Praetorian over 11 years@Nawaz Works just fine on gcc4.7.2, can't post a demo link since LWS is down. Here's an ideone demo.
-
David Doria over 11 years@Nawaz, this should be valid though, right? 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; } };
-
HostileFork says dont trust SE over 11 years...also there's nothing stopping one from doing a mix and match if someone explicitly specializes and doesn't use the default. So you get situations like
intPoint.MyFunction<float>()
which are probably incorrect. A static assert in the body that makes sure T matched the same type you tested U against is necessary as well. :-/ -
David Doria over 11 yearsI've seen this solution, but it seems to really corrupt the readability of the code.
-
Kerrek SB over 11 years@DavidDoria: The original code is too contrived to make a more adapted suggestion.
-
Kerrek SB over 11 yearsI actually like this. I don't think I'd ever use it, but the OP's example is so contrived that this is indeed a good solution.
-
David Doria over 11 years@Praetorian I made a new answer that uses your suggestion but does not move the SFINAE trigger to the return type (to avoid the invalid c++11 that Nawaz mentioned). Thoughts?
-
Praetorian over 11 years@HostileFork Good point, added another example that uses
static_assert
to prevent explicit specializations of the member templates. -
Praetorian over 11 years@David I don't know whether to believe Nawaz that C++11 changed the rules and broke a ton of
enable_if
code out there. The example in your answer should work too; I prefer the dummy template argument as opposed to the dummy function argument, but that's just personal preference. Also, I've posted another example that usesstatic_assert
to prevent the user from explicitly specializing the member template. -
David Doria over 11 years@Praetorian I do not like the dummy function argument, but I also don't want to write code that is going to break with the next gcc :) (as Nawaz suggests this might). Is there a way to use a dummy template argument and have a return type of void? Also, what is the ... called in this context so I can look it up?
-
Praetorian over 11 years@David I assume you meant use dummy template argument and have return type other than
void
. The second, optional, template argument forenable_if
is the type argument, so usetypename enable_if<expr, SomeType>::type
, and the resulting type will beSomeType
. The...
in the answer is variadic templates, another C++11 feature. -
HostileFork says dont trust SE over 11 years@DavidDoria If your case is that you are only using SFINAE to check if certain types are
is_same
(then having a default if those don't match) then template-specializingPoint
for those fixed types would be exactly what you wanted. You'd have the same instantiation with more readable definitions. -
David Doria over 11 years@Praetorian Ah I see, I didn't realize there was a default of 'void' as the second enable_if parameter. So with the variable number of template parameters, it does not work like functions where the "fixed" parameters have to go first and then the "remaining arguments" are passed to the "..." (i.e. you have Dummy... before T=U).
-
Xeo over 11 years@Nawaz: Where exactly does the C++11 standard say you can't do that anymore? That'd be a pretty daring breaking change, IMO, and I can't see why they should do it. Also, if it was true, the (now) idiomatic
template<class T> auto f(T& v) -> decltype(v.foo());
SFINAE construct, which checks for a member, wouldn't work. -
Nawaz over 11 years@Xeo: Compare the wordings of $14.8.2/2 from C++03 with the wordings of $14.8.2/8 from C++11. According to C++11, this doesn't seem to be SFINAE, rather it is an error. See this topic also : stackoverflow.com/questions/12015938/…
-
Xeo over 11 years@Nawaz: That doesn't in any way suggest that substitution in a return type is not a soft-error anymore. The problem in that other thread is that inside of
meta<int>
, you get the error, which never happens withenable_if
. -
Janek Olszak over 9 yearsstatic_cast<T*>(nullptr)
-
Pharap over 4 yearsTechnically Praetorian's suggestion doesn't change the return type, it changes the argument type (i.e. by removing the default argument).
-
Pharap over 4 yearsThe question is specifically tagged C++11,
if constexpr
is only available from C++17 onwards. Also themutable
isn't needed and is probably a bad idea asmutable
usually is. -
sdd over 4 years@Praetorian perhaps you could just add
static_assert(std::is_same<U, T>::value, "Do not specify template arguments!");
to break compilation. Variadic args just serve to a bit more informative error message (correct types in it).