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
.
为了便于说明,我展示了两个略有不同的小模板递归定义。一个使用 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
.