С++ 11: разрешение перегрузки и SFINAE
Я изучаю SFINAE, и это моя первая попытка напечатать "ДА" только для тех типов, которые вы можете выводить с помощью std::ostream
(забудьте о std::operator<<(std::ostream &, T)
сейчас...):
template <typename T>
void f(const T &) { std::cout << "NO" << std::endl; }
template <typename T, int SFINAE = sizeof(static_cast<std::ostream &(std::ostream::*)(T)>(
&std::ostream::operator<<))>
void f(const T &) { std::cout << "YES" << std::endl; }
Хотя они, похоже, работают с f(std::vector<int>())
(уступая "НЕТ" ), компилятор жалуется, что f(0)
неоднозначен: http://ideone.com/VljXFh
prog.cpp:16:5: error: call of overloaded 'f(int)' is ambiguous
f(0);
^
prog.cpp:6:6: note: candidate: void f(const T&) [with T = int]
void f(const T &) { std::cout << "NO" << std::endl; }
^
prog.cpp:10:6: note: candidate: void f(const T&) [with T = int; int SFINAE = 8]
void f(const T &) { std::cout << "YES" << std::endl; }
^
Как я могу исправить свой код? Является ли версия "ДА" не более конкретной, чем версия "НЕТ", которая является полностью общей?
Разъяснение
Все f(0)
, f(0.)
и f(true)
терпят неудачу с той же "двусмысленной" ошибкой. Я ищу решение, применимое ко всем типам, принятым std::ostream::operator<<
. В идеале он не должен полагаться на определение вспомогательного типа, который "заглушает" пространство имен.
Ответы
Ответ 1
Версия NO
по-прежнему действительна для int
, и не существует соответствующего частичного заказа для выбора между двумя перегрузками, поэтому вызов неоднозначен.
Один простой способ устранения неоднозначности - добавить дополнительный аргумент тега к функциям:
template <typename T>
void f(const T &, char) { std::cout << "NO" << std::endl; }
// ^^^^
template <typename T, int SFINAE = sizeof(static_cast<std::ostream &(std::ostream::*)(T)>(
&std::ostream::operator<<))>
void f(const T &, int) { std::cout << "YES" << std::endl; }
// ^^^
Теперь, когда вы вызываете функцию, просто передайте дополнительный 0
(или напишите вспомогательную функцию, чтобы сделать это для вас). Защищенная функция SFINAE будет предпочтительнее, если она действительна, потому что int
лучше, чем char
для 0
. См. в этой статье для более чистого способа выражения этой неоднозначности.
В качестве альтернативы вы можете написать признак, чтобы проверить, действителен ли оператор для данного типа, затем используйте std::enable_if<check<T>>
и std::enable_if<!check<T>>
, чтобы избежать аргумента, вызывающего неоднозначность.
Кстати, вы можете использовать decltype
и возвращающие возвращаемые типы для этого вида SFINAE, и я думаю, что он выглядит немного чище:
template <typename T>
void f(const T &, char) { std::cout << "NO" << std::endl; }
template <typename T>
auto f(const T &t, int) -> decltype(std::declval<std::ostream&>() << t, void())
{ std::cout << "YES" << std::endl; }
Когда мы получим С++ Concepts, вы сможете сделать что-то вроде этого (это работает в GCC с включенной концепцией):
template <typename T>
concept bool Outputtable = requires (T t, std::ostream o) { o << t; };
template <typename T>
void f(const T &) { std::cout << "NO" << std::endl; }
template <Outputtable T>
void f(const T &) { std::cout << "YES" << std::endl; }
Ответ 2
Поскольку С++ 17 можно будет описать с помощью std::void_t
:
template<typename, typename = std::void_t<>>
struct enables_ostream_output : std::false_type {};
template<typename Type>
struct enables_ostream_output<
Type,
std::void_t<decltype(std::declval<std::ostream>() << std::declval<Type>())>
> : std::true_type {};
в сочетании с классическим std::enable_if
:
template <typename Type>
typename std::enable_if<!enables_ostream_output<Type>::value>::type
f(const Type &) { std::cout << "NO" << std::endl; }
template <typename Type>
typename std::enable_if<enables_ostream_output<Type>::value>::type
f(const Type &) { std::cout << "YES" << std::endl; }
Другой вариант, рекомендованный @TartanLlama, заключается в использовании std::(experimental::)is_detected
как:
template<typename Type>
using ostream_output_t = decltype(std::declval<std::ostream>() << std::declval<Type>());
а затем:
template <typename Type>
typename std::enable_if<!std::is_detected<ostream_output_t, Type>::value>::type
f(const Type &) { std::cout << "NO" << std::endl; }
template <typename Type>
typename std::enable_if<std::is_detected<ostream_output_t, Type>::value>::type
f(const Type &) { std::cout << "YES" << std::endl; }