Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enable_if with is_enum does not work

Tags:

c++

c++11

sfinae

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

like image 272
M.M Avatar asked Apr 21 '15 04:04

M.M


People also ask

What is the purpose of std :: Enable_if?

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.

What is enable if?

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.

Is enum class C++?

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.


3 Answers

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.

like image 127
Yakk - Adam Nevraumont Avatar answered Sep 27 '22 23:09

Yakk - Adam Nevraumont


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 )
{
    // ...
}
like image 30
Praetorian Avatar answered Sep 27 '22 21:09

Praetorian


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;
}
like image 23
R Sahu Avatar answered Sep 27 '22 21:09

R Sahu