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


person Christian Rau    schedule 14.02.2012    source источник
comment
Непонятно, что is_signed/is_unsigned взаимоисключающие (привет char?). Попробуйте сделать так, чтобы во второй версии вместо этого было !std::is_signed<T>::value.   -  person Kerrek SB    schedule 15.02.2012
comment
Можете ли вы попробовать использовать std::is_signed<T>::value для одного из участников и !std::is_signed<T>::value для другого? Это просто для того, чтобы убедиться, что не существует какого-то типа с несовместимыми настройками для is_signed и is_unsigned.   -  person Dietmar Kühl    schedule 15.02.2012
comment
@KerrekSB и Дитмар Ха, получилось! Не могу поверить, что это было так просто. Если кто-то добавит это в качестве ответа, я приму это.   -  person Christian Rau    schedule 15.02.2012
comment
@Kerrek char не является ни целочисленным типом со знаком, ни целочисленным типом без знака. Но IIRC is_signed и is_unsigned позаботятся об этом: только один из них сообщит true за char.   -  person Johannes Schaub - litb    schedule 15.02.2012
comment
@JohannesSchaub-litb: Вы правы, char в порядке. Однако перечисления и указатели всегда ложны; Я думаю, потому что они не арифметические типы.   -  person Kerrek SB    schedule 15.02.2012


Ответы (3)


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

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

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

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

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

person Johannes Schaub - litb    schedule 14.02.2012
comment
Замена is_unsigned на !is_signed (или наоборот) сработала, поэтому я предполагаю (хотя и не очень хорошо разбираюсь в глубинах спецификации языка, не говоря уже о шаблонах), что есть какой-то тип, который и подписан, и подписан. Или это может быть потому, что обе версии этих шаблонов по умолчанию оцениваются как false для неарифметических типов? Но опять же, есть также is_integral для устранения неоднозначности (для целочисленных типов это должно быть взаимоисключающим, не так ли?). - person Christian Rau; 15.02.2012
comment
@Christian Я понятия не имею, что они делают, но это определенно неправильное поведение. Попробуйте !!is_unsigned вместо !is_signed. Я бы тоже не удивился, увидев, что это работает :) - person Johannes Schaub - litb; 15.02.2012
comment
Ха, тоже сработало. Хорошо, теперь я думаю, что это действительно становится немного смешным. Вероятно, вы правы в том, что здесь виноват компилятор. - person Christian Rau; 15.02.2012
comment
Два выражения, включающие параметры шаблона, считаются эквивалентными, если два определения функций, содержащих выражения, удовлетворяют правилу одного определения... Не могли бы вы объяснить это подробнее, добавив пример в свой пост? - person Prasoon Saurav; 22.02.2012
comment
Ваш ответ верен только наполовину. Компилятор правильно отклонил код (см. мой ответ ниже), но дал неправильную причину. - person Walter; 26.04.2014
comment
@walter он явно передает аргумент - person Johannes Schaub - litb; 26.04.2014
comment
Действительно он делает. Странный. Какой смысл в SFINAE, если вы это делаете? - person Walter; 26.04.2014

Лично я бы избегал 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>());
}
person ildjarn    schedule 14.02.2012
comment
+1 Хорошая альтернатива. Но в качестве примечания я думаю, что последний аргумент версии с плавающей запятой должен быть std::true_type, поскольку числа с плавающей запятой всегда подписаны (по крайней мере, обычные реализации, для не-IEEE мой код преобразования все равно не будет работать). - person Christian Rau; 15.02.2012
comment
@Christian: Ты совершенно прав; Я основывал логику на документах MSDN для is_signed, которые оказались неверными (сюрприз, сюрприз). Фиксированный. - person ildjarn; 15.02.2012

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

person Walter    schedule 21.04.2014
comment
Разве это не должно иметь те же проблемы неоднозначности, что и SFINAE в типе аргумента? Есть ли какое-то правило, которое заставляет это работать, а версию аргумента - нет? В противном случае это, кажется, не так много решает вопрос. - person Christian Rau; 26.04.2014
comment
Существует очень веская причина не использовать SFINAE для типа аргумента, см. отредактированный ответ. - person Walter; 26.04.2014