С++: попытка инкапсулировать std::enable_if в классе

Я как раз использовал SFINAE для выбора шаблонов функций, когда мне пришла в голову великолепная идея инкапсулировать std::enable_if в структуру, подобную этой.

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

а потом использовать его, например, вот так

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

Однако это не работает, как видно из вызова

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

С другой стороны, пытаясь создать

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

один получает ошибку компилятора ("тип не является членом std::enable_if").

В чем причина такого поведения? Я спрашиваю, потому что из моего небольшого знания SFINAE я бы подумал, что ошибка в выводе типа приводит к исключению перегрузки ...?


Для полноты изложенную выше проблему можно решить с помощью псевдонимов шаблонов.

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

Есть ли альтернатива использованию структуры вместо псевдонима шаблона?

EDIT: Здесь я имею в виду реализацию, которая решает общую проблему. Например вот это

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

у меня не работает, потому что для частичной специализации нужен простой идентификатор. Если бы это было так, можно было бы заменить std::is_same на общие структуры Condition<T,U>. Любые альтернативы?


person davidhigh    schedule 02.09.2014    source источник
comment
Поскольку вам не хватает typename перед аргументом по умолчанию EnableWhenEqual, я предполагаю, что вы используете какую-то версию VS старше 2013 года. Как только вы добавите typename, ваш код не удается скомпилировать ни gcc, ни clang, ни VS2013 (error C2783: 'void foo(T &&,U &&)' : could not deduce template argument for 'Enable')   -  person Praetorian    schedule 02.09.2014
comment
Использование EnableWhenEqual<T,U>::type требует создания экземпляра EnableWhenEqual, что приводит к созданию экземпляров всех объявлений членов. Член typedef иногда будет недопустимым, но эта ошибка не возникает в непосредственном контексте шаблона функции foo (SFINAE == SFITICINAE = ошибка подстановки в непосредственном контексте не является ошибкой).   -  person dyp    schedule 02.09.2014
comment
Конечно, вы можете повторить то, что делает enable_if, то есть использовать специализацию шаблона: template<typename T, typename U> struct EnableWhenEqual {}; template<typename T> struct EnableWhenEqual<T,T> { using type = void; };   -  person dyp    schedule 02.09.2014
comment
Спасибо за ваши ответы. Добавление имени типа действительно приводит к ошибкам компилятора, как объясняет dyp. Что касается вопроса о реализации на основе структуры, я искал не этот частный пример, а общее решение. Я отредактировал вопрос, чтобы сделать его более понятным.   -  person davidhigh    schedule 02.09.2014
comment
Что не так с использованием std::enable_if и std::enable_if_t (С++ 14)? или псевдонимы шаблонов? Зачем вообще хотеть что-то вроде enable_if_same<>? Его единственное преимущество в том, что он немного короче, чем std::enable_if_t<std::is_same<>::value>, но он вводит еще одну черту типа, которую нужно запомнить, хотя он тривиально создается из существующих.   -  person Walter    schedule 02.09.2014
comment
@Walter: В этом нет ничего плохого, за исключением того, что списки параметров шаблона могут быть довольно длинными. Поэтому я искал альтернативу, в которой условия оцениваются аккуратно организованным образом, чего также нельзя сказать о решении, полученном из-1_. Кроме того, конечно, type-trait выгоден, если условие вычисляется много раз, как это имеет место в моем коде.   -  person davidhigh    schedule 03.09.2014
comment
В качестве точки стиля вам не нужно давать имя параметру шаблона, если он не указан в определении: template <typename T, typename = std::enable_if<...>::type> void foo(T&& v) {...}   -  person Oktalist    schedule 03.09.2014


Ответы (2)


Это должно помочь:

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

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

person Horstling    schedule 02.09.2014

Другой вид инкапсуляции:

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

обратите внимание, что когда вы задаете вопросы об обработке шаблонов VS, пожалуйста, указывайте точную версию Visual Studio. Поддержка шаблонов VS чрезвычайно причудлива.

person Yakk - Adam Nevraumont    schedule 02.09.2014