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
In C++ metaprogramming, std::enable_if is an important function to enable certain types for template specialization via some predicates known at the compile time. Using types that are not enabled by std::enable_if for template specialization will result in compile-time error.
Introduction. The enable_if family of templates is a set of tools to allow a function template or a class template specialization to include or exclude itself from a set of matching functions or specializations based on properties of its template arguments.
C++11 has introduced enum classes (also called scoped enumerations), that makes enumerations both strongly typed and strongly scoped. Class enum doesn't allow implicit conversion to int, and also doesn't compare enumerators from different enumerations. To define enum class we use class keyword after enum keyword.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With