enable_if работает вне класса, но не внутри

Вот мой несколько странный код:

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) {}  

Если я ifdef из метода b и позвонить b<int *>(pi) где pi является int *все компилируется.

Если я ifdef вне функции b (вне класса) и вызов A<int *> a; a.b(pi)Я получаю следующую ошибку:

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

Почему несоответствие и как я могу исправить проблему, чтобы я мог использовать методы в A?

3 ответа

Решение

Проблема в том, что SFINAE работает только во время разрешения перегрузки и только если сама функция является шаблоном. В вашем случае с методом весь класс является шаблоном, то есть не существует подстановки параметра шаблона (помните: SFINAE == "Ошибка замены не является ошибкой").

В момент создания экземпляры сигнатуры методов выглядят следующим образом (не обращая внимания на их вызов):

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

Чтобы это исправить, сделайте шаблоны методов тоже:

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
};

Напомним, что вывод аргумента шаблона является лучшим способом использования SFINAE, поэтому вы должны изменить свободные функции так:

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

В C++11 вы даже можете использовать параметры шаблона для SFINAE:

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

Использование псевдонима из записи в блоге, ссылки на которую приведены здесь:

namespace detail{ enum class enabler{}; }

template<class Cond, class T = detail::enabler>
using EnableIf = typename std::enable_if<C::value, T>::type;

Для объяснения см. Ответ Xeo.

Для обходного пути: просто добавьте фиктивный параметр шаблона в метод

#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);
}

Демо здесь.

Вы не используете SFINAE правильно, потому что компилятор не может вывести аргумент для enable_if<...>::type и, вероятно, именно поэтому это не удается.

Правильные объявления автономных функций:

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);

В этом конкретном случае также может использоваться перегрузка простой функции:

template <typename T>  
void b(T);

template <typename T>  
void b(T*);
Другие вопросы по тегам