5

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?

Praetorian
  • 100,267
  • 15
  • 224
  • 307
Viacheslav Kroilov
  • 1,111
  • 1
  • 9
  • 20
  • 1
    There is also the possibility to use tag dispatching which would be much more elegant for your usage example. `defaultSizeHelper(std::integral_constant{})` – Denis Blank Oct 14 '15 at 23:01
  • Just a small addition to my previous comment, your code would look like: http://coliru.stacked-crooked.com/a/ee0738c2369e03ec when using tag dispatching. – Denis Blank Oct 14 '15 at 23:16

2 Answers2

5

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{});
Praetorian
  • 100,267
  • 15
  • 224
  • 307
3

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.

Community
  • 1
  • 1
Filip Roséen - refp
  • 58,021
  • 18
  • 139
  • 186
  • 1
    This solution is better (generally, not in this particular case) because it doesn't introduce an additional dummy (well, not at all) argument. – Viacheslav Kroilov Oct 14 '15 at 22:56