SFINAE различие между подписанным и неподписанным
У меня есть функции для преобразования различных арифметических типов в тип с плавающей точкой половинной точности (просто uint16_t
на самом низком уровне), и у меня есть различные функции для целочисленных типов и типов с плавающей запятой, используя SFINAE и std::enable_if
:
template<typename T>
uint16_t to_half(typename std::enable_if<
std::is_floating_point<T>::value,T>::type value)
{
//float to half conversion
}
template<typename T>
uint16_t to_half(typename std::enable_if<
std::is_integral<T>::value,T>::type value)
{
//int to half conversion
}
Они вызываются внутренне из универсального шаблонного конструктора путем явной реализации:
template<typename T>
half::half(T rhs)
: data_(detail::conversion::to_half<T>(rhs))
{
}
Это компилируется и также отлично работает. Теперь я пытаюсь провести различие между целыми числами со знаком и без знака, заменив вторую функцию двумя функциями:
template<typename T>
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value &&
std::is_signed<T>::value,T>::type value)
{
//signed to half conversion
}
template<typename T>
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value &&
std::is_unsigned<T>::value,T>::type value)
{
//unsigned to half conversion
}
Но как только я пытаюсь скомпилировать это VS2010 дает мне
ошибка C2995:
"uint16_t math::detail::conversion::to_half( std::enable_if<std::tr1::is_integral<_Ty>::value && std::tr1::is_signed<_Ty>::value, T>::type )"
: шаблон функции уже определен.
Таким образом, кажется, что он не может устранить неоднозначность между двумя шаблонами, но у него, очевидно, не было проблем с интегральной версией наряду с версией с плавающей запятой.
Но так как я не так уж много интересуюсь шаблонным фокусником, я могу просто упустить что-то здесь очевидное (или, может быть, это должно сработать, и это просто ошибка VS2010). Так почему же это не работает и как можно заставить его работать с минимальными затратами на программирование, насколько это возможно, и в рамках только стандартных функций (если вообще возможно)?
3 ответа
Если это не работает, значит, ваш компилятор ошибся.
Два выражения, содержащие параметры шаблона, считаются эквивалентными, если два определения функции, содержащие выражения, удовлетворяют одному правилу определения...
Это самое важное правило, которое следует учитывать здесь (пропущены детали "..."). Два ваших шаблона не удовлетворяют ODR, поскольку их последовательности токенов различаются.
Два функциональных шаблона эквивалентны, если они объявлены в одной и той же области, имеют одно и то же имя, имеют идентичные списки параметров шаблона и имеют возвращаемые типы и списки параметров, которые эквивалентны с использованием описанных выше правил для сравнения выражений, включающих параметры шаблона.
Таким образом, ваши два шаблона определяют разные шаблоны и не конфликтуют. Теперь вы можете проверить, являются ли ваши шаблоны "функционально эквивалентными". Они будут, если для любого возможного набора аргументов шаблона, ваш enable_if
выражение всегда будет давать одно и то же значение. Но так как это не так для is_unsigned
а также is_signed
это тоже не так. Если это так, то ваш код будет некорректным, но без диагностики (что фактически означает "неопределенное поведение").
Лично я бы максимально избегал SFINAE, так как вы можете сделать то же самое с перегрузкой:
template<typename T>
uint16_t to_half_impl(T val, std::true_type, std::true_type)
{
// is_integral + is_signed implementation
}
template<typename T>
uint16_t to_half_impl(T val, std::true_type, std::false_type)
{
// is_integral + is_unsigned implementation
}
template<typename T>
uint16_t to_half_impl(T val, std::false_type, std::true_type)
{
// is_floating_point implementation
}
template<typename T>
typename std::enable_if<std::is_arithmetic<T>::value, uint16_t>::type to_half(T val)
{
return to_half_impl(val, std::is_integral<T>(), std::is_signed<T>());
}
Более распространенной идиомой является использование SFINAE для возвращаемого типа, а не для типа аргумента. В противном случае тип шаблона T
не может быть вычитаемым. С
// from C++14
template<bool C, typename T> using enable_if_t = typename std::enable_if<C,T>::type;
template<typename T>
enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, uint16_t>
to_half(T value)
{
//signed to half conversion
}
template<typename T>
enable_if_t<std::is_integral<T>::value && !std::is_signed<T>::value, int16_t>
to_half(T value)
{
//unsigned to half conversion
}
тип T
в следующем заявлении
auto y=to_half(x); // T is deduced from argument, no need for <T>
выводим (даже тривиально), но для вашего исходного кода это не так! Действительно, при запуске этого заявления с вашим to_half()
реализация через clang дает
test.cc:24:11: error: no matching function for call to 'to_half'
auto x= to_half(4);
^~~~~~~
test.cc:7:10: note: candidate template ignored: couldn't infer template argument 'T'
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value &&
^
test.cc:15:10: note: candidate template ignored: couldn't infer template argument 'T'
uint16_t to_half(typename std::enable_if<std::is_integral<T>::value &&
^
Конечно, если явно указать аргумент шаблона (как вы это сделали), эта проблема не появится. Таким образом, ваш код не был неправильным (кроме компилятора), но какой смысл в SFINAE, если вы передаете тип аргумента шаблона?