SFINAE различие между подписанным и неподписанным - PullRequest
8 голосов
/ 15 февраля 2012

У меня есть функции для преобразования различных арифметических типов в тип с плавающей запятой половинной точности (просто 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 ]

8 голосов
/ 15 февраля 2012

Лично я бы здесь максимально избегал 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 голосов
/ 15 февраля 2012

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

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

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

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

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

1 голос
/ 21 апреля 2014

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...