Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SFINAE doesn't work on a constexpr function?

To support portability I want to choose a constant based on the fact whether size_t is 32 bit or 64 bit. The code:

using namespace std;

namespace detail {
    template<enable_if<is_same<size_t, uint32_t>::value,void*>::type = nullptr>
    constexpr static const size_t defaultSizeHelper() {
        return ( (size_t) 1 << 30 ) / 2 * 5; //2,5 Gb
    }
    template<enable_if<is_same<size_t, uint64_t>::value,void*>::type = nullptr>
    constexpr size_t defaultSizeHelper() {
        return numeric_limits<size_t>::max() / 2;
    }
}

constexpr static size_t defaultSize = detail::defaultSizeHelper();

This code doesn't compile because of the error: 'std::enable_if<false, void*>::type' has not been declared. template<enable_if<is_same<size_t, uint64_t>::value,void*>::type = nullptr>

Compiler - GCC 4.9

It seems to me that the compiler doesn't apply a SFINAE principle to a constexpr. What should I do then?

like image 725
Viacheslav Kroilov Avatar asked Oct 14 '15 22:10

Viacheslav Kroilov


2 Answers

The principle behind SFINAE is that if the substitution of a deduced template argument results in ill-formed code, then that function template is dropped from the overload resolution set, instead of causing a hard error.

In your case there is no deduced template argument or substitution of one, hence you end up with compilation errors. All you need is

constexpr static size_t defaultSize = is_same<size_t, uint32_t>::value 
                                        ? (( (size_t) 1 << 30 ) / 2 * 5)
                                        : numeric_limits<size_t>::max() / 2;

For curiosity's sake, if you wanted to use SFINAE, you could do something like this

namespace detail {
    template<typename T, typename enable_if<is_same<T, uint32_t>::value,void*>::type = nullptr>
    constexpr static const T defaultSizeHelper(T) {
        return ( (size_t) 1 << 30 ) / 2 * 5; //2,5 Gb
    }
    template<typename T, typename enable_if<is_same<T, uint64_t>::value,void*>::type = nullptr>
    constexpr T defaultSizeHelper(T) {
        return numeric_limits<size_t>::max() / 2;
    }
}

constexpr static size_t defaultSize = detail::defaultSizeHelper(size_t{});
like image 112
Praetorian Avatar answered Oct 23 '22 16:10

Praetorian


The Problem

SFINAE stands for Substitution Failure Is Not An Error.

Neither of your two templates fail during instantiation, instead one of them will fail the second the compiler takes a look at it (because it will see that the enable_ifs does not depend on a template parameter, and try to expand them directly).


Solution

The solution is to make the check depend on some template-parameter, so that the compiler can only check the condition upon a potential instantiation.

In your case the easiest solution will be to simply provide a default template-argument that is the type that you would like to check against (T in the below).

using namespace std;

namespace detail {
    template<class T = uint32_t, typename enable_if<is_same<size_t, T>::value,void*>::type = nullptr>
    constexpr static const size_t defaultSizeHelper() {
        return ( (size_t) 1 << 30 ) / 2 * 5; //2,5 Gb
    }

    template<class T = uint64_t, typename enable_if<is_same<size_t, T>::value,void*>::type = nullptr>
    constexpr size_t defaultSizeHelper() {
        return numeric_limits<size_t>::max() / 2;
    }
}

constexpr static size_t defaultSize = detail::defaultSizeHelper();

Note: An alternative solution would be to combine the two functions into one, and use the ternary-operator to either return the result of one expression, or another..

Note: Now that the check is dependent on a template-parameter, make sure you understand why you need to use typename to disambiguate the enable if. See this answer for more information.

like image 3
Filip Roséen - refp Avatar answered Oct 23 '22 16:10

Filip Roséen - refp