SFINAE 不适用于 constexpr 函数?

SFINAE doesn't work on a constexpr function?

为了支持可移植性,我想根据 size_t 是 32 位还是 64 位这一事实来选择一个常量。 代码:

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();

由于 error: 'std::enable_if<false, void*>::type' has not been declared. template<enable_if<is_same<size_t, uint64_t>::value,void*>::type = nullptr>

此代码无法编译

编译器 - GCC 4.9

在我看来,编译器并未将 SFINAE 原则应用于 constexpr。那我该怎么办?

SFINAE 背后的原则是,如果对推导的模板参数的替换导致格式错误的代码,那么该函数模板将从重载决策集中删除,而不是导致硬错误。

在您的情况下,没有推导的模板参数或模板参数的替换,因此您最终会遇到编译错误。你只需要

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

出于好奇,如果你想使用 SFINAE,你可以这样做

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代表替换失败不是错误.

你的两个模板在实例化过程中都没有失败,相反,其中一个会在编译器检查它的时候失败(因为它会看到 enable_ifs不依赖于模板参数,直接展开即可。


解决方案

解决方案是使检查依赖于一些模板参数,这样编译器只能在潜在实例化时检查条件。

在您的情况下,最简单的解决方案是简单地提供一个 默认模板参数,这是您要检查的类型(T 在下面)。

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.