C++ 17 元编程递归结构:enum 或 constexpr

C++ 17 Metaprogramming recursive struct: enum or constexpr

为了便于说明,我展示了两个略有不同的小模板递归定义。一个使用 enum,另一个使用 static constexpr 来定义一个值。

我检查了两个程序的输出程序集,它们完全相同,而且在语义上它们也看起来相同。

我认为 constexpr 可能更现代一些,但是使用 enum/static constexpr 之间有什么区别吗?重要吗?

// using enum
template<uint64_t N>
struct Sum {
    enum : uint64_t { value = N + Sum<N - 1>::value };
};

template<>
struct Sum<0> {
    enum : uint64_t { value = 1 };
};
// using static constexpr
template<uint64_t N>
struct Sum {
    static constexpr uint64_t value = N + Sum<N - 1>::value;
};

template<>
struct Sum<0> {
    static constexpr uint64_t value = 1;
};

提取值:

#define sum(n) (Sum<n>::value)

最显着的区别(因为 C++17 避免了对静态数据成员的 out-of-class 定义的需要)是枚举数 一起被实例化包含 class,而静态数据成员仅在 需要 时实例化。 (但是请注意,至少 MSVC 并不总是正确地延迟它们。)

当你有几个这样的常量并且其中一些只对某些专业化有意义时,这很重要。 错误 在像 T::maybe_exists 这样的初始值设定项中,不会通过在静态数据成员情况下实例化 class 来触发。

另一个区别:大约 ODR-used.

正如@Igor Tandetnik 所说,当您使用 &Sum<0>::value 时,它仅在 value 是一个 static constexpr 变量时才有效。那是因为 value 是枚举数的文字,而不是变量。但注意,&value 是一个 ODR-used,它要求 value 一个且只有一个 定义某处。所以你必须在 XXX.cpp 处声明 uint64_t const Sum<0>::value; 以提供定义,在 C++17 之前(static constexpr 变量在 C++17 之后隐式地是一个 inline 变量,而一个inline变量在翻译单元中允许有多个定义)。

也许您认为“我根本不会使用 &value”。但是在另一种情况下你会遇到麻烦:uint64_t const& a = value,它也是 ODR-used。对于枚举器,其生命周期将通过引用延长(如 reference initialization)。但是 static constexpr 变量不会要求定义,就像使用 &value 一样。当你使用一些按引用传递的函数时,它总是会引起一些麻烦,比如 std::find.