“surprising” constant initialization because of definition order

后端 未结 4 2049
醉梦人生
醉梦人生 2021-02-19 11:58

When reading the slides about constexpr the introduction is about \"surprisingly dynamic initialization with consts\". The example is

struct S {
    st         


        
相关标签:
4条回答
  • 2021-02-19 12:44

    You can find out whether a constant is statically or dynamically initialised by trying to declare an array:

    struct S {
        static const int c;
    };
    const int d = 10 * S::c; // (1)
    const int S::c = 5;      // (2)
    
    static char array[d];
    

    This code fails in g++ version 4.7.0, because d is dynamically initialised. And if you exchange (1) and (2), it compiles, because now d is statically initialised. But I can't find another way to fix it, using constexpr.

    0 讨论(0)
  • 2021-02-19 12:54

    For static initialization one needs, roughly speaking, a constant-expression initializer.

    To be a constant-expression, roughly speaking, a variable needs to be of a const type and have a preceding initialization with a constant-expression.

    In the first example d's initializer is not a constant-expression, as S::c isn't one (it has no preceding initialization). Hence, d is not statically initialized.

    In the second example d's initializer is a constant-expression, and everything is OK.

    I'm simplifying matters. In full formal standardese this would be about nine times longer.


    As for constexpr specifier, no object has to be declared constexpr. It is just an additional error-check. (This is about constexpr objects, not constexpr functions).

    You may declare S::c constexpr in the second variant if you want some extra error protection (perhaps 5 will start changing its value tomorrow?) Adding constexpr to the first variant cannot possibly help.

    0 讨论(0)
  • 2021-02-19 12:59

    In the first example, d is not initialized by a constant expression, because S::c is not

    a non-volatile const object with a preceding initialization, initialized with a constant expression

    (see C++11 [expr.const]p2, bullet on lvalue-to-rvalue conversions), because the initialization of S::c does not precede the initialization of d. Therefore static initialization will be used for S::c (because it is initialized by a constant expression), but dynamic initialization can be used for d.

    Since static initialization precedes dynamic initialization, d would be initialized to 50 by its dynamic initializer. The compiler is permitted to convert the dynamic initialization of d to static initialization, but if it does, it must produce the value that d would have had if every variable which could have used dynamic initialization had, in fact, used dynamic initialization. In this case, d is initialized to 50 either way. See C++11 [basic.start.init]p2 for more information on this.

    There is no way to add constexpr to the first example to guarantee that static initialization is used for d; in order to do that, you must reorder the initializations. However, adding constexpr will produce a diagnostic for the first example, which will at least allow you to ensure that dynamic initialization is not used (you get static initialization or a compilation error).

    You can update the second case to ensure that static initialization is used as follows:

    struct S {
        static const int c; // do not use constexpr here
    };
    constexpr int S::c = 5;
    constexpr int d = 10 * S::c;
    

    It is ill-formed to use constexpr on a variable declaration which is not a definition, or to use it on a variable declaration which does not contain an initializer, so const, not constexpr must be used within the definition of struct S. There is one exception to this rule, which is when defining a static constexpr data member of a literal, non-integral type, with the initializer specified within the class:

    struct T { int n; };
    struct U {
        static constexpr T t = { 4 };
    };
    constexpr T U::t;
    

    In this case, constexpr must be used in the definition of the class, in order to permit an initializer to be provided, and constexpr must be used in the definition of the static data member, in order to allow its use within constant expressions.

    0 讨论(0)
  • 2021-02-19 13:05

    I believe that the rules laid out in 3.6.2 to determine when static initialization happens do not include the initialization for d, which is therefore dynamic initialization. On the other hand, S::c is indeed statically initialized (since 5 is a constant expression). Since all static initialization happens before dynamic initialization, you get the expected result.

    To make d eligible for static initialization, it has to be initialized with a constant expression. This in turn forces you to write the S::c inline:

    struct S { static constexpr int c = 5; };
    
    const int d = S::c; // statically initialized
    

    Note that the standard permits dynamic initialization to be replaced by static initialization, which is why reordering the two lines in your original example will cause the two different sorts of initialization. As TonyK points out, you can use array[d] in the static case, but not in the dynamic case, so you can check which one is happening. With the constexpr approach, you're guaranteed to have static initialization and you don't have to rely on optional compiler behaviour.

    0 讨论(0)
提交回复
热议问题