15
votes

My understanding is that constexpr globals of class type are all but unusable because

  • Such an object must be defined in every TU, because constexpr does not permit forward declaration of an object.

  • Default linkage as static would cause naming the object (ODR-use or not) in an inline function to violate the ODR, because the respective inline definitions would have different meaning.

  • Declaration as extern constexpr with one definition per TU would violate the ODR rule if the object is ODR-used, which occurs when a reference to it is taken.

    • A reference is taken for an implicit this parameter, even if it's unused by a member function.
    • Obviously happens if you try to pass the object by reference.
    • Also happens if you try to pass the object by value, which implicitly uses a copy or move constructor, which by definition passes by reference.
    • GCC and Clang both complain of ODR violations (multiple definitions) if an object is declared extern constexpr even if not ODR-used.

Is this all correct? Is there any way to have a constexpr global of class type without wrapping it in an inline function?

1
Your second point is wrong I believe. Using the value of a constant does not odr-use it. Indeed the standard uses constexpr variables with internal linkage (see std::allocator_arg as an example). The ODR doesn't care what names you use inside a function for constant expressions. - Simple
@Simple The function would violate the ODR, not the object. Two definitions of an inline function in the same header included in two TUs must satisfy the ODR rule, part of which is that each name must refer to the same entity. It's possible that std::allocator_arg leaves the user in a quandary, but printing its address from an inline function easily exposes UB. - Potatoswatter
If you need its address then obviously you violate the ODR, but you run into the same problem with non-class types. See 3.2/2: A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied. - Simple
@Simple See the list of sub-bullets for things that quietly take the address. This is why I specified class type for this question; lvalue-to-rvalue conversion of a class type implies ODR-use by a constructor and negates that escape clause, unlike non-class types. - Potatoswatter
I don't think your point about this is correct. See 5.19/2 [expr.const]: this (5.1) unless it appears as the postfix-expression in a class member access expression, including the result of the implicit transformation in the body of a non-static member function and: an invocation of a function other than a constexpr constructor for a literal class or a constexpr function. Passing by reference to a constexpr function also doesn't odr-use it due to function invocation substitution. I had a question open on this point a while ago. - Simple

1 Answers

1
votes

Global constexpr variables can ODR-safely be defined in headers using a bit of macro magic and the proverbial extra level of indirection

#define PP_GLOBAL_CONSTEXPR_VARIABLE(type, var, value)                   \
namespace var##detail {                                                  \
template<class = void>                                                   \
struct wrapper                                                           \
{                                                                        \
     static constexpr type var = value;                                  \
};                                                                       \
template<class T>                                                        \
constexpr type wrapper<T>::var;                                          \
}                                                                        \
namespace {                                                              \
auto const& var = var##detail::wrapper<>::var;                           \
}

The macro provides a reference inside an unnamed namespace to an object instance in an implementation class template.

Each object in an unnamed namespace inside a header generates a unique instance in every translation unit that includes its header. Furthermore, to prevent ODR violations, it is important that the objects in e.g. multiple instantiations of a function template are the same.

However, for references it doesn't matter that they have a different identity; as long as they refer to the same object instance in an implementation class template.

You can wrap this macro in a header and safely include it in many TUs without a problem.

See the following discussion on the Boost mailinglist for more details: http://lists.boost.org/Archives/boost/2007/06/123380.php