2012-02-14 2 views
7

У меня есть функции для преобразования различных арифметических типов в тип с плавающей точкой с половинной точностью (только на uint16_t на самом низком уровне), и у меня есть разные функции для типов источников с целыми числами и с плавающей точкой, используя SFINAE и std::enable_if :Дифференциация SFINAE между подписанным и неподписанным

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

Непонятно, что 'is_signed' /' is_unsigned' является взаимоисключающим (hello 'char'?). Попробуйте сделать вторую версию: '! Std :: is_signed :: value' вместо. –

+0

Можете ли вы попытаться использовать 'std :: is_signed :: value' для одного из членов и'! Std :: is_signed :: value' для другого? Это просто, чтобы убедиться, что существует не только некоторый тип, который имеет непоследовательные параметры для 'is_signed' и' is_unsigned'. –

+0

@ KerrekSB & Dietmar Hah, вот и все! Не могу поверить, что это было так просто. Если кто-то добавит его в качестве ответа, я соглашусь с ним. –

ответ

3

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

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

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

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

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

+0

Заменили 'is_unsigned' на'! Is_signed' (или наоборот), поэтому я предполагаю (хотя и не так хорошо разбираюсь в глубине спецификации языка, не говоря уже о шаблонах), что есть какой-то тип, который подписан и назначается. Или это может быть потому, что версии этих шаблонов по умолчанию оцениваются как «false» для неарифметических типов? Но опять же, есть и 'is_integral', чтобы устранить вещи (для интегральных типов он должен быть взаимоисключающим, не так ли?). –

+0

@Christian Я понятия не имею, что они делают, но это определенно неправильное поведение. Попробуйте '!! is_unsigned' вместо'! Is_signed'. Я бы не удивился, увидев, что он тоже «работает» :) –

+0

ХА, это сработало. Хорошо, теперь я думаю, что это действительно немного смешно. Вероятно, вы правы в том, что компилятор здесь виноват. –

7

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

+1 Хорошая альтернатива. Но, как примечание, я думаю, что последний аргумент для версии с плавающей запятой должен быть из 'std :: true_type', поскольку поплавки всегда подписаны (по крайней мере, обычные реализации, для не-IEEE мой код конверсии не будет работать в любом случае). –

+0

@ Христиан: Ты совершенно прав; Я основывал логику с документами MSDN для 'is_signed', которые оказались неправильными (удивление, удивление). Исправлена. – ildjarn

1

Более общий идиома использовать 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() реализации через лязг дает

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

+0

Должно ли это иметь те же проблемы с неопределенностью, что и SFINAE в типе аргумента? Есть ли какое-то правило, которое делает эту работу, но аргумент версии нет? В противном случае, похоже, это не так важно. –

+0

Существует очень хорошая причина ** не использовать SFINAE для типа аргумента, см. Отредактированный ответ. – Walter

Смежные вопросы