15

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?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • 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 Dec 04 '13 at 08:14
  • @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 Dec 04 '13 at 08:21
  • 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 Dec 04 '13 at 08:24
  • And 3.2/5: *except that a name can refer to a const object with internal or no linkage if the object has the same literal type in all definitions of D, and the object is initialized with a constant expression (5.19), and the value (but not the address) of the object is used, and the object has the same value in all definitions of D* – Simple Dec 04 '13 at 08:25
  • 1
    @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 Dec 04 '13 at 08:30
  • 1
    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 Dec 04 '13 at 08:36
  • @Simple The ODR-use occurs even if it's unused by the member function because it initializes a parameter, however unused. Function invocation substitution has been removed for C++14, and C++11 specified that it never affected well-formedness anyway. – Potatoswatter Dec 04 '13 at 09:07
  • Anyway, whether a use of `this` is permissible in a core constant expression has no bearing on whether use of an lvalue to initialize `this` is an ODR-use. – Potatoswatter Dec 04 '13 at 09:15
  • Related: [How do I forward-declare a constexpr object at namespace scope?](http://stackoverflow.com/q/19958868/420683) – dyp Dec 04 '13 at 10:50

1 Answers1

1

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

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • Ah, static members of templates are coalesced! Why isn't the reference `constexpr` too? And why not do `template< class = void >` and be rid of the placeholder? – Potatoswatter Dec 04 '13 at 11:51
  • @Potatoswatter I don't think you can write `auto constexpr&`, in any case, what would be the difference? – TemplateRex Dec 04 '13 at 11:53
  • Because `const` makes a (core) constant expression only for integer types. I'm not sure that access to a `constexpr` object through a reference yields `constexpr` semantics, although that should be the case. `constexpr auto const &` would be the syntax, although that would perhaps qualify the reference illegally. – Potatoswatter Dec 04 '13 at 11:56
  • @Potatoswatter the `placeholder` was a remnant of the Boost code that I adapted for my own utility toolbox. I think you are correct that it can be removed by using a default `void` template parameter. – TemplateRex Dec 04 '13 at 12:00
  • @Potatoswatter did the macro not work for you? or is there another reason the answer is no longer acceptable? – TemplateRex Dec 04 '13 at 13:10
  • Hmm, on second thought, access to `var` is just as ODR unsafe as access to an object declared `static`. The reference is an entity resulting from name lookup, and resolution to an identical object in every TU is immaterial. Edit: Sorry, it "works" fine due to ODR non-diagnosis; these issues are fairly academic and this is really just an exercise. I'm going with a more conservative idiom for my real-world solution. – Potatoswatter Dec 04 '13 at 13:13
  • @Potatoswatter I think a `constexpr` reference is legal; it is initialized with a *reference constant expression*. The initializer must be a static object. Access through a reference is allowed if the reference has been initialized with a constant expression. clang++3.4 accepts an `constexpr auto&` declaration. For templates, the ODR specifies that the program shall behave *as if there were a single definition* (if the requirements are fulfilled). – dyp Dec 05 '13 at 12:32
  • @DyP Ah good, 7.1.5/9 says there is no implicit `const` qualification on a reference declaration marked `constexpr`. Still though, my concern is about use of equal but non-identical references in `inline` functions and such violating the ODR. – Potatoswatter Dec 05 '13 at 13:11
  • @Potatoswatter the Boost discussion I linked to contains a message by Dave Abrahams that stated that since a reference was not an "entity", they were a loophole for ODR. However, the current Standard explicitly lists references as such [basic]/3, so maybe this changed for C++11. – TemplateRex Dec 05 '13 at 13:22
  • @TemplateRex That interpretation is certainly in error. C++98 3.2/5 is the same as C++11: "corresponding names, looked up according to 3.4, shall refer to … the same entity …" This is not a generic, separate concept of an entity but just any name lookup result. – Potatoswatter Dec 05 '13 at 23:43