0

I was just using SFINAE to select function templates when I had the glorious idea to encapsulate std::enable_if in a struct like this

template<typename T, typename U>
struct EnableWhenEqual
{
    typedef typename std::enable_if<std::is_same<T, U>::value>::type type;
};

and then use it, for example, like this

template<typename T, typename U, typename Enable = EnableWhenEqual<T,U>::type >
void foo(T&& t, U&& u)
{
    if (std::is_same<T,U>::value)
        std::cout << "OK, overload allowed." << std::endl;
    else
        std::cout << "Bad. Should not compile!" << std::endl;
}

However, this doesn't work, as one sees by invoking

foo(1,1);     //prints "OK, overload allowed"
foo(1,2.0);   //prints "Bad, should not compile", but obviously does

On the other hand, by trying to create

EnableWhenEqual<int,int>();      //compiles
EnableWhenEqual<int,double>();   //does not compile because type does not exist

one obtains a compiler error ("type is not a member of std::enable_if").

What is the reason for this behaviour? I'm asking because from my little SFINAE knowledge I would have thought that an error in the type-deduction leads to exclusion of the overload ... ?


For completeness, the above problem can be solved using template aliasing

template <typename T, typename U>
using EnableWhenEqual = typename std::enable_if<std::is_same<T,U>::value>::type;

Is there also an alternative using a struct instead of a template alias?

EDIT: Here I mean an implementation which solves the general problem. For example, this here

template<typename T, typename U, bool B, typename C=void>
struct EnableWhenEqual {};

template<typename T, typename U, typename C>
struct EnableWhenEqual<T,U,std::is_same<T,U>::value>
{
    typedef typename C type;
};

don't works for me because the partial specialization needs a simple identifier. If it would, one could replace std::is_same by general structs Condition<T,U>. Any alternatives?

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • 3
    Since you're missing a `typename` before the `EnableWhenEqual` default argument, I'm guessing you're using some VS version older than 2013. Once you add the `typename`, your code [fails to compile](http://coliru.stacked-crooked.com/a/b2c296e2d8de0ec0) on both gcc and clang, as well as VS2013 (`error C2783: 'void foo(T &&,U &&)' : could not deduce template argument for 'Enable'`) – Praetorian Sep 02 '14 at 18:51
  • 5
    Using `EnableWhenEqual::type` requires instantiating `EnableWhenEqual`, which leads to instantiating all member declarations. The member typedef will sometimes not be legal, but this error does not occur in the immediate context of the function template `foo` (SFINAE == SFITICINAE = Substitution Failure In The Immediate Context Is Not An Error). – dyp Sep 02 '14 at 19:00
  • You can of course replicate what `enable_if` is doing, i.e. use template specialization: `template struct EnableWhenEqual {}; template struct EnableWhenEqual { using type = void; };` – dyp Sep 02 '14 at 19:03
  • Thank you for your answers. Adding typename indeed leads to compiler errors as explained by dyp. Regarding the question for a struct - based implementation, I was looking not for this special example but for a general solution.I've edited the question to make it clearer. – davidhigh Sep 02 '14 at 19:53
  • What's wrong with using `std::enable_if` and `std::enable_if_t` (C++14)? or template aliases? Why would one ever want something like `enable_if_same<>`? Its only advantage that it's slightly shorter than `std::enable_if_t::value>`, but it introduces yet another type traits to remember, though it's trivially constructed from existing ones. – Walter Sep 02 '14 at 19:57
  • @Walter: Nothing's wrong with it, except that template parameter lists can get quite long. Therefore I was seeking for an alternative where the conditions are evaluated in a neatly arranged way -- which is also not the case for the derived-from-`enable_if` solution. Besides, of course, a type-trait is advantageous if one evaluate the condition many times, which is the case in my code. – davidhigh Sep 02 '14 at 20:07
  • As a style point, you don't need to give the template parameter a name if it's not referenced in the definition: `template ::type> void foo(T&& v) {...}` – Oktalist Sep 02 '14 at 20:14

2 Answers2

5

This should do the trick:

template<typename T, typename U>
struct EnableWhenEqual : std::enable_if<std::is_same<T, U>::value>
{
};

http://coliru.stacked-crooked.com/a/650202ba3d42d34b

Horstling
  • 2,131
  • 12
  • 14
2

A different kind of encapsulation:

template<class T, class U>
using EnableWhenEqual_t = typename std::enable_if< std::is_same<T,U>::value >::type;

template<typename T, typename U, typename Enable = EnableWhenEqual_t<T,U> >
void foo(T&& t, U&& u) {
  if (std::is_same<T,U>::value)
    std::cout << "OK, overload allowed." << std::endl;
  else
    std::cout << "Bad. Should not compile!" << std::endl;
}

note when you are asking questions about VS treatment of templates, please include the exact visual studio version. VS template support is extremely quirky.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524