enable_if with is_enum does not work

2019-03-30 10:38发布

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

标签: c++ c++11 sfinae
3条回答
Lonely孤独者°
2楼-- · 2019-03-30 11:09

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 )
{
    // ...
}
查看更多
Evening l夕情丶
3楼-- · 2019-03-30 11:17

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;
}
查看更多
爷、活的狠高调
4楼-- · 2019-03-30 11:32
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.

查看更多
登录 后发表回答