Разделение 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). Итак, почему эта работа не работает и как она может работать с как можно меньшим количеством служебных программ программирования и в рамках стандартных возможностей (если возможно)?

Ответ 1

Если это не работает, ваш компилятор ошибочен.

Два выражения с параметрами шаблона считаются эквивалентными, если два определения функций, содержащие выражения, удовлетворяют одному правилу определения...

Это самое важное правило для рассмотрения здесь (не указаны детали "..." ). Ваши два шаблона не удовлетворяют ODR, потому что их токены последовательности отличаются.

Два шаблона функций эквивалентны, если они объявлены в одной области, имеют одинаковое имя, имеют одинаковые списки параметров шаблона и имеют типы возвращаемых значений и списки параметров, которые эквивалентны с использованием правил, описанных выше, для сравнения выражений, содержащих параметры шаблона.

Итак, ваши два шаблона определяют разные шаблоны и не конфликтуют. Теперь вы можете проверить, являются ли ваши шаблоны "функционально эквивалентными". Они были бы, если бы для любого возможного набора аргументов шаблона, выражение enable_if всегда давало бы такое же значение. Но так как это не верно для is_unsigned и is_signed, это тоже не так. Если бы это было так, то ваш код был бы плохо сформирован, но без необходимости диагностики (что фактически означает "поведение undefined" ).

Ответ 2

Лично я мог бы избегать 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>());
}

Ответ 3

Более распространенная идиома заключается в использовании 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, если вы передаете тип аргумента шаблона?