C++ 17 Metaprogramming recursive struct: enum or constexpr

Viewed 345

For illustrative purposes, I'm showing two small, slightly different templated recursive definitions. One uses an enum and the other uses static constexpr to define a value.

I have inspected the output assembly from both programs, and they are exactly the same, and semantically they also appear the same.

I think constexpr is maybe a bit more modern, but are there any differences between using enum/static constexpr, or are there any particular use cases where the difference would really matter?

// 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;
};

Extracting value:

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

The most significant difference (since C++17 avoids the need for an out-of-class definition for the static data member) is that enumerators are instantiated along with the containing class, whereas static data members are instantiated only when needed. (Note, however, that MSVC, at least, doesn’t always properly defer them.)

This matters when you have several such constants and some of them make sense only for certain specializations. Errors in an initializer like T::maybe_exists will not be triggered by instantiating the class in the static data member case.

another difference: about ODR-used.

as @Igor Tandetnik said, when you use &Sum<0>::value, it will only work if value is a static constexpr variable. that's because, value is a literal for an enumerator, instead of a variable. but pay attention, &value is an ODR-used, which demands value has one and only one definition somewhere. so you have to declare uint64_t const Sum<0>::value; at XXX.cpp to provide the definition, before C++17 (static constexpr variable is implicitly an inline variable after C++17, and an inline variable is allowed to have more than one definitions among translation units).

maybe you think "I won't use &value at all". but you will be in trouble at another case: uint64_t const& a = value, which is also ODR-used. for an enumerator, its lifetime will be extended by the reference (as reference initialization). but static constexpr variable will not and demands the definition, just like using &value. it always cause some trouble when you use some function passing by reference, such as std::find.

Related