Search code examples
c++templatesc++11constexprenable-if

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?


Solution

  • 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.