4

Here is my somewhat odd code:

template <typename T&>
class A {  
public:  
  void b(typename std::enable_if<!std::is_pointer<T>::value, T>;::type o) {}  
  void b(typename std::enable_if<std::is_pointer<T>::value, T>;::type o) {}  
};  

template <typename T>  
void b(typename std::enable_if<!std::is_pointer<T>::value, T>::type o) {}  
template <typename T>  
void b(typename std::enable_if<std::is_pointer<T>::value, T>::type o) {}  

If I ifdef out the method b and call b<int *>(pi) where pi is int *, everything compiles.

If I ifdef out the function b (outside class) and call A<int *> a; a.b(pi), I get the following error:

error: no type named 'type' in 'std::__1::enable_if<false, int *>'

Why the inconsistency and how can I fix the problem so that I can use the methods in A?

Xeo
  • 129,499
  • 52
  • 291
  • 397
Fred Finkle
  • 1,947
  • 3
  • 18
  • 21

3 Answers3

9

The problem is, that SFINAE only works during overload resolution and only if the function itself is a template. In your method case, the whole class is a template, meaning that there is no substitution of the template parameter (remember: SFINAE == "Substitution Failure Is Not An Error").

At the point of instantiation, the method signatures look like this (nevermind the call to them):

void A<int*>::b(std::enable_if<false, int*>::type o) // error
void A<int*>::b(std::enable_if<true, int*>::type o)

To fix this, make the methods templates too:

template<class T>
class A{
public:
  template<class U>
  void b(U o, typename std::enable_if<!std::is_pointer<U>::value>::type* = 0){}
  // same for the other version
};

On a side note, letting the template argument get deduced is the better way to use SFINAE, so you should modify the free functions to look like this:

template<class T>
void b(T o, typename std::enable_if<!std::is_pointer<T>::value>::type* = 0){}
// same for the other version

In C++11, you can even use the template parameters for SFINAE:

template<class T, EnableIf<std::is_pointer<T>> = {}>
void b(T o);

Utilizing an alias from the blog entry linked from here:

namespace detail{ enum class enabler{}; }

template<class Cond, class T = detail::enabler>
using EnableIf = typename std::enable_if<C::value, T>::type;
Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • It would be good to explain how to "transform" the methods by adding dummy template arguments to re-activate SFINAE on them. – Matthieu M. Jun 28 '12 at 16:08
4

For the explanation, see Xeo's answer.

For the work-around: just add a dummy template parameter to the method

#include <utility>
#include <type_traits>

template <typename T>
class A {  
public:
  template <typename U = T>
  void b(typename std::enable_if<!std::is_pointer<U>::value, U>::type o);

  template <typename U = T>
  void b(typename std::enable_if<std::is_pointer<U>::value, U>::type o);
};

template <typename T>
template <typename U>
void A<T>::b(typename std::enable_if<!std::is_pointer<U>::value, U>::type o) {}  

template <typename T>  
template <typename U>
void A<T>::b(typename std::enable_if<std::is_pointer<U>::value, U>::type o) {} 


int main() {
    A<int> a;
    a.b(0);
}

Live Demo Here.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • This does not help, the method signature is fixed, the substitution happens at the instantiation point of `A`. Also, the free functions don't look quite right. :P – Xeo Jun 28 '12 at 16:16
  • @Xeo: those were not intended to be free-functions but definitions of the methods (to illustrate that the default should not be repeated). Anyway I patched it up. – Matthieu M. Jun 28 '12 at 16:44
0

You are not using SFINAE correctly because the compiler can't deduce the argument for enable_if<...>::type and probably that's why it fails.

Correct declarations of free-standing functions would be:

template <typename T>  
typename std::enable_if<!std::is_pointer<T>::value, void>::type b(T o);

template <typename T>  
typename std::enable_if<std::is_pointer<T>::value, void>::type b(T o);

In this particular case plain function overloading can be used as well:

template <typename T>  
void b(T);

template <typename T>  
void b(T*);
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • What? Using `enable_if` as the OP does is perfectly fine, if the member methods themselves were templates. – Xeo Jun 28 '12 at 15:59