Использование std::enable_if с параметрами анонимного типа

Я пытаюсь использовать std::enable_if с неиспользуемым и безымянным параметром типа, чтобы не искажать тип return. Однако следующий код не компилируется.

#include <iostream>

template <typename T, typename = std::enable_if_t<!std::is_integral<T>::value>>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
  foo<float>();
  foo<int>();
}

Компилятор говорит:

7:3: error: redefinition of 'template<class T, class> T foo()'
4:3: note: 'template<class T, class> T foo()' previously declared here
 In function 'int main()':
11:12: error: no matching function for call to 'foo()'
11:12: note: candidate is:
4:3: note: template<class T, class> T foo()
4:3: note: template argument deduction/substitution failed:

В чем проблема? Как мне изменить код, чтобы он скомпилировался? Учебник "Discovering Modern C++" явно поощряет использование std::enable_if с параметрами анонимного типа.

РЕДАКТИРОВАТЬ: я знаю, что это работает, если я помещу std::enable_if в возвращаемый тип. Однако я намерен получить более подробную информацию о том, почему это не работает, если я использую его с параметрами анонимного типа. Как я уже сказал, мой учебник поощряет вариант с использованием параметров анонимного типа, поэтому мне интересно, почему мой код не компилируется.


person user1494080    schedule 25.10.2016    source источник
comment
Вы пытались поместить std::enable_if в возвращаемый тип?   -  person DeiDei    schedule 26.10.2016
comment
Проверьте этот вопрос: stackoverflow.com/questions/15427667/   -  person DeiDei    schedule 26.10.2016


Ответы (4)


Однако я намерен получить более подробную информацию о том, почему это не работает, если я использую его с параметрами анонимного типа.

Значения по умолчанию не участвуют в разрешении перегрузки, поэтому вы фактически переопределяете ту же функцию.

Давайте упростим ваш пример:

template<typename = int>
void f() {}

template<typename = void>
void f() {}

int main() {
    f<>();
}

Приведенный выше код не компилируется, поскольку он не может знать, какую версию f вы хотите вызвать.

В вашем случае, если я вызываю foo как foo<void, void>, у меня почти такая же проблема.
Компилятор не может угадать мое намерение, и тот факт, что второй параметр имеет значение по умолчанию, не означает, что вы не можете передать в другом типе.

Из-за этого код имеет неправильный формат, и компилятор корректно выдает ошибку.


В качестве примечания, вы все еще можете работать без использования std::enable_if_t в возвращаемом типе.
В качестве примера:

#include <type_traits>
#include <iostream>

template <typename T, std::enable_if_t<!std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, std::enable_if_t<std::is_integral<T>::value>* = nullptr>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
    foo<float>();
    foo<int>();
}

Пока я пытался выяснить, что было (неправильным) предположением ОП, и объяснить, почему это может быть так, @T.C. правильно указал на фактическую причину в комментариях к этому ответу.
Стоит процитировать его комментарий, чтобы добавить больше деталей к ответу:

Это не разрешение перегрузки; это соответствие объявления. Во-первых, нет двух перегрузок, чтобы возникла какая-либо двусмысленность. Это две ошибки переопределения: шаблон функции и аргумент шаблона по умолчанию.

person skypjack    schedule 25.10.2016
comment
Забудьте о призыве; проблема в том, что вы переопределяете один и тот же шаблон функции. - person T.C.; 26.10.2016
comment
@Т.С. Я сказал это в первой строке. Вызов был еще одним примером того, как он может создать проблемы, если его разрешить. Должен ли я удалить его из ответа? - person skypjack; 26.10.2016
comment
Это не разрешение перегрузки; это соответствие объявления. Во-первых, нет двух перегрузок, чтобы возникла какая-либо двусмысленность. Это две ошибки переопределения: шаблон функции и аргумент шаблона по умолчанию. - person T.C.; 26.10.2016
comment
@Т.С. Я понимаю вашу точку зрения, и я не говорю, что вы неправы (ну, я думаю, что никогда не скажу, что вы не правы на самом деле - разрыв в навыках очевиден), но я попытался выяснить неправильное рассуждение ОП, которое было (в мое скромное мнение), что значения параметров шаблона по умолчанию участвуют в разрешении перегрузки. Это все. Если вы согласны, я объединяю ответ с вашим комментарием, чтобы дать более подробную информацию. - person skypjack; 26.10.2016

Вы можете поместить enable_if в возвращаемый тип:

template <typename T>
std::enable_if_t<!std::is_integral<T>::value,T>
foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T>
std::enable_if_t<std::is_integral<T>::value, T>
foo() { std::cout << "integral" << std::endl; return T(); }

Кстати, enable_if_t доступно в C++14, так что вместо этого вы можете сказать typename std::enable_if<std::is_integral<T>::value, T>::type. Довольно много.

Но немного более идиоматично (и читабельно) было бы отправлять на основе типа:

template <typename T>
T foo_impl(std::false_type) { std::cout << "non-integral" << std::endl; return T(); }

template <typename T>
T foo_impl(std::true_type) { std::cout << "integral" << std::endl; return T(); }

template <typename T>
T foo(){
    return foo_impl<T>(typename std::is_integral<T>::type{});
}
person krzaq    schedule 25.10.2016

Есть несколько способов, которыми вы можете отключить функции SFINAE. Обычно вам следует воздерживаться от добавления дополнительного параметра функции/шаблона и просто смешиваться с возвращаемым типом.

template <typename T>
auto foo() -> std::enable_if_t<!std::is_integral<T>::value, T>
{ std::cout << "non-integral" << std::endl; return T(); }

template <typename T>
auto foo() -> std::enable_if_t<std::is_integral<T>::value, T>
{ std::cout << "integral" << std::endl; return T(); }
person DeiDei    schedule 25.10.2016
comment
Я знаю, что это сработает, если я поставлю std::enable_if в возвращаемый тип. Однако я намерен получить более подробную информацию о том, почему это не работает, если я использую его с параметрами анонимного типа. Как я уже сказал, мой учебник поощряет вариант с использованием параметров анонимного типа, поэтому мне интересно, почему мой код не компилируется. Похоже, вы поощряете обратное. Каковы причины? - person user1494080; 26.10.2016
comment
@user1494080 user1494080 Я поддерживаю обратное, потому что добавление дополнительных параметров шаблона или дополнительных параметров функции, как в ответе выше, может позволить пользователю сделать следующее: foo<float, int>(); или foo<float>(nullptr); Мы не хотим, чтобы это было разрешено. Это может сломать вещи. Кроме того, вы редко можете ошибиться с типом возврата SFINAE. - person DeiDei; 26.10.2016

Ваша ошибка в том, что вы используете enable_if_t справа от знака равенства.

Вы должны использовать его слева

#include <iostream>
#include <type_traits>

template <typename T, std::enable_if_t<!std::is_integral<T>::value, int> = 0>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
  foo<float>();
  foo<int>();
}

Но эта работа с C++14.

В С++ 11 (ваш вопрос помечен как С++ 11) у вас нет enable_if_t.

Код становится

#include <iostream>
#include <type_traits>

template <typename T,
          typename std::enable_if<!std::is_integral<T>::value, int>::type = 0>
T foo() { std::cout << "non-integral" << std::endl; return T(); }

template <typename T,
          typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T foo() { std::cout << "integral" << std::endl; return T(); }

int main() {
  foo<float>();
  foo<int>();
}
person max66    schedule 25.10.2016
comment
Вы должны использовать его слева ‹-- это просто неправда. - person Barry; 26.10.2016
comment
@Barry - извините: не уверен, что понял: это неверно, вы имеете в виду, что он может использовать std::enable_if_t другими способами (например: включить (или нет) возвращаемое значение)? - person max66; 26.10.2016
comment
Нет, я говорю, что на самом деле вы можете использовать enable_if_t как тип по умолчанию (точно так же, как вы можете использовать любой другой тип). Ошибка здесь в том, что OP пытается предоставить два разных типа по умолчанию для одного и того же шаблона функции. - person Barry; 26.10.2016
comment
@Barry - я пытаюсь сказать это своим словом (пожалуйста, поправьте меня, если я ошибаюсь). OP определяет две версии f() с одной и той же подписью шаблона (typename, typename); это дает ошибку, потому что sfinae может давать разные значения по умолчанию для второго аргумента typename, но не может изменить подпись; это дает коллизию, компилятор не может выбрать и выдает ошибку. В моем примере sfinae включает или нет второй параметр typename, поэтому измените (или нет) сигнатуру шаблона функции; поэтому компилятор видит один f() (для каждого вызова) и компилирует без ошибок. - person max66; 26.10.2016