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?
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{});
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With