MCVE:
#include <type_traits>
template<typename T>
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
{
}
enum class Bar { a,b,c };
int main()
{
Bar bar{Bar::a};
func(bar, 1);
}
I expect func(bar, 1);
to match my definition of func
however g++ reports:
sfi.cc: In function 'int main()':
sfi.cc:13:17: error: no matching function for call to 'func(Bar&, int)'
func(bar, 1);
^
sfi.cc:13:17: note: candidate is:
sfi.cc:4:10: note: template<class T> bool func(typename std::enable_if<std::is_e
num<_Tp>::value, T>::type&, int)
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
^
sfi.cc:4:10: note: template argument deduction/substitution failed:
sfi.cc:13:17: note: couldn't deduce template parameter 'T'
func(bar, 1);
^
Why isn't this working and how do I fix it?
Background: This was an attempted solution to this problem
template<typename T>
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
T
is used above in a non-deduced context. This means it won't deduct the T
, as it (in the general case) requires reversing an arbitrary turing-complete transformation, which is impossible.
What func
has is that the first argument is a enum class Bar
, and the second is an int
. From this you expect it to deduce T
.
While setting T
to enum class Bar
does solve the problem, C++ doesn't guess. It pattern matches.
Suppose we had:
template<class T>
struct blah { using type=int; };
template<>
struct blah<int> { using type=double; };
then
template<class T>
bool func( typename blah<T>::type );
If someone passes a int
to func
, what type should be deduced for T
? This is a toy example: foo<T>::type
can execute a Turing-complete algorithm to map T
to the type in question. Inverting that or even determining if the inverse is ambiguous, is not possible in the general case. So C++ doesn't even try, even in the simple cases, as the edge between simple and complex gets fuzzy quickly.
To fix your problem:
template<class T,class=typename std::enable_if< std::is_enum<T>::value >::type>
bool func( T &t, int x ) {
}
now T
is used in a deduced context. The SFINAE still occurs, but doesn't block template type deduction.
Or you can wait for C++1z concepts, which automate the above construct (basically).
Looking at the linked question, the easy way to solve your problem is with tag dispatching.
template<typename T>
bool func(T &t, int x)
{
// do stuff...
}
However I would like to have three different function bodies:
We have 3 cases:
T being an enum
T being unsigned char
Everything else
so, dispatch:
namespace details {
template<class T>
bool func( T& t, int x, std::true_type /* is_enum */, std::false_type ) {
}
template<class T>
bool func( T& t, int x, std::false_type, std::true_type /* unsigned char */ ) {
}
template<class T>
bool func( T& t, int x, std::false_type, std::false_type ) {
// neither
}
}
template<class T>
bool func( T& t, int x ) {
return details::func( t, x, std::is_enum<T>{}, std::is_same<unsigned char, T>{} );
}
now normal overload rules are used to pick between the 3 functions. If you somehow have a type that is both enum
and unsigned char
(impossible), you get a compile-time error.
You're using the template argument T
in a non-deduced context.
From §14.8.2.5/5 [temp.deduct.type]
The non-deduced contexts are:
— The nested-name-specifier of a type that was specified using a qualified-id.
— ...
To fix the problem move the enable_if
to a dummy template parameter
template<typename T,
typename = typename std::enable_if< std::is_enum<T>::value, T >::type>
bool func( T &t, int x )
{
// ...
}
Live demo
Looking at the question you linked to, you're trying to switch between two definitions of func
based on whether the first argument is an enum
. In that case the above solution will not work because a default template argument is not part of the function template's signature, and you'll end up with multiple definition errors.
There are two different ways to fix that, use a dummy template parameter
template<typename T,
typename std::enable_if< std::is_enum<T>::value, int >::type* = nullptr>
bool func( T &t, int x )
{
// ...
}
or use the enable_if
expression in the return type
template<typename T>
typename std::enable_if< std::is_enum<T>::value, bool >::type
func( T &t, int x )
{
// ...
}
The errors you are seeing have to do with automatic type deduction than enable_if
and is_enum
.
The following works.
#include <iostream>
#include <type_traits>
template<typename T>
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
{
return true;
}
enum class Bar { a,b,c };
int main()
{
Bar bar{Bar::a};
std::cout << func<decltype(bar)>(bar, 1) << std::endl;
}
Update
As already discovered by the OP, use of decltype
can be avoided by using a wrapper function.
#include <iostream>
#include <type_traits>
template <typename T>
bool func( typename std::enable_if< std::is_enum<T>::value, T >::type &t, int x )
{
return true;
}
template <typename T>
bool func2( T &t, int x )
{
return func<T>(t,x);
}
enum class Bar { a,b,c };
int main()
{
Bar bar{Bar::a};
std::cout << func2(bar, 1) << std::endl;
}