SFINAE differentiation between signed and unsigned

Deadly 提交于 2019-12-19 01:46:32

问题


I have functions for converting different arithmetic types to a half precision floating point type (just a uint16_t on the lowest level) and I have different functions for integer and floating point source types, using SFINAE and 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
}

These are called internally from a universal templated constructor by explicit instantiation:

template<typename T>
half::half(T rhs)
    : data_(detail::conversion::to_half<T>(rhs))
{
}

This compiles and also works just fine. Now I try to differentiate between signed and unsigned integers, by replacing the second function with the two functions:

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
}

But once I try to compile this VS2010 gives me

error 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 )": function template already defined.

So it seems it cannot disambiguate between the two templates, but it obviously had no problems with the integral version alongside the floating point version.

But since I'm not that much a a template magician I may just be missing something obvious here (or maybe it should actually work and is just a VS2010 bug). So why doesn't this work and how can it be made work with as few programming overhead as possible and in the limits of standard-only features (if even possible)?


回答1:


If this doesn't work then your compiler is at error.

Two expressions involving template parameters are considered equivalent if two function definitions containing the expressions would satisfy the one definition rule ...

That's the most important rule to consider here (left out the details of "..."). Your two templates do not satisfy the ODR because their token sequences differ.

Two function templates are equivalent if they are declared in the same scope, have the same name, have identical template parameter lists, and have return types and parameter lists that are equivalent using the rules described above to compare expressions involving template parameters.

So your two templates define different templates and do not clash. You could now check whether your templates perhaps are "functionally equivalent". They would be if for any possible set of template arguments, your enable_if expression would always yield the same value. But since that is not true for is_unsigned and is_signed, this is not the case either. If it would, then your code would be ill-formed, but without requiring a diagnostic (which effectively means "undefined behavior").




回答2:


Personally, I would avoid SFINAE here as much as possible since you can accomplish the same thing with overloading:

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:


The more common idiom is to use SFINAE on the return type rather than the argument type. Otherwise, the template type T may not be deducible. With

// 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
}

the type T in the following statement

auto y=to_half(x);    // T is deduced from argument, no need for <T> 

is deducible (even trivially), but for your original code it is not! Indeed, when running this statement with your to_half() implementation through clang gives

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 &&
         ^

Of course, if one explicitly provides the template argument (as you did), this problem does not appear. So your code wasn't wrong (but the compiler), but what's the point of SFINAE if you pass the template argument type?



来源:https://stackoverflow.com/questions/9285657/sfinae-differentiation-between-signed-and-unsigned

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!