8

This is probably a bit of an unusual question, in that it asks for a fuller explanation of a short answer given to another question and of some aspects of the C++11 Standard related to it.

For ease of reference, I shall sum up the referenced question here. The OP defines a class:

struct Account 
{
    static constexpr int period = 30;
    void foo(const int &) { }
    void bar() { foo(period); } //no error?
};

and is wondering why he gets no error about his usage of an in-class initialized static data member (a book mentioned this to be illegal). Johannes Schaub's answer states, that:

  1. This violates the One Definition Rule;
  2. No diagnostics is required.

As much as I rely the source and validity of this answer, I honestly dislike it because I personally find it too cryptic, so I tried to work out a more meaningful answer myself, with only partial success. Relevant seems to be § 9.4.2/4:

"There shall be exactly one definition of a static data member that is odr-used (3.2) in a program; no diagnostic is required" [Emphases are mine]

Which gets me a bit closer to the point. And this is how § 3.2/2 defines an odr-used variable:

"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" [Emphases are mine]

In the OP's question, variable period clearly satisfies the requirements for appearing in a constant expression, being a constexpr variable. So the reason must be certainly found in the second condition: "and the lvalue-to-rvalue conversion (4.1) is immediately applied".

This is where I have troubles interpreting the Standard. What does this second condition actually mean? What are the situations it covers? Does it mean that a static constexpr variable is not odr-used (and therefore can be in-class initialized) if it is returned from a function?

More generally: What are you allowed to do with a static constexpr variable so that you can in-class initialize it?

Community
  • 1
  • 1
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • I see that we have very [relevant yet distinct questions](http://stackoverflow.com/questions/14547923/why-passing-a-static-in-class-initialized-member-to-a-function-taking-const-refe). Both arising from the same base Q and answer :) – Alok Save Jan 27 '13 at 13:52
  • @AlokSave: indeed it seems so. At least I'm not the only one finding this whole thing a bit confusing. – Andy Prowl Jan 27 '13 at 13:55
  • To be honest I did a fair bit of head scratching trying to make sense of the standerdese but I couldn't really see through it clearly.You actually managed to express the thought process quite well throughout in your question.Me I made a more generic question fearing my perceptions of the standardese might actually influence someone reading it to make sense. +1 for documenting your research :) – Alok Save Jan 27 '13 at 14:03
  • @AlokSave: I liked the "*fearing my perceptions of the standardese might actually influence someone reading it to make sense*" part. I'm confronted with this feeling almost every time I have to ask for the meaning of some formal definition which is obscure to me (not just in C++ or programming). – Andy Prowl Jan 27 '13 at 14:06
  • i didnt put the "cryptic" term "one definition rule" into my answer. only when someone wanted to know for some reason where in the standard the rule i mentioned is located, i mentioned it. – Johannes Schaub - litb Jan 27 '13 at 14:16
  • @JohannesSchaub-litb: the *non-cryptic* term "one definition rule" is *not* included in your answer, right, but this fact just contributes to make your answer cryptic. Your sentence "A violation of this rule does not require a diagnostic. So behavior is effectively undefined." states the *what*, and I trust that, but does not explain the *why*, and I don't like that. – Andy Prowl Jan 27 '13 at 14:19
  • @andy referencing the one definition rule does not state "why". it states "yes this is the name of the referenced rule". that seems most useless to tell to the questioner. there is a big difference of explaining someone the Standard and of explaining someone C++. – Johannes Schaub - litb Jan 27 '13 at 14:49
  • the *easy to grasp* explanation of the behavior has been given to him by the book. he does not need to know about the concepts in the Standard that are used to give this behavior. Nor did he ask for them. His question was solely why his compiler does not give a diagnostic, and my answer answered that in a short and to the point way. – Johannes Schaub - litb Jan 27 '13 at 14:58
  • @JohannesSchaub-litb: I'm not saying the ODR states "why", I'm saying that *you* should have explained the OP *why*. True, no diagnostic is required, but where is this specified? I know most of the time quoting the standard is not needed, but this question is non-trivial and the test *seemed* to disprove the assertion in the book. So in this case a lengthier explanation would have been appropriate. But that's just my opinion. I will edit my question to make it clear that it is just my opinion. – Andy Prowl Jan 27 '13 at 15:13
  • "where is this specified" is not an answer of "why is no diagnoatic required". from the fact that the questioner didn't mention any interest in the Standard and obviously made the impression of not being able to comprehend it currently, I made the reasonable choice to not confuse him with concepts of the Standard. – Johannes Schaub - litb Jan 27 '13 at 15:21
  • @JohannesSchaub-litb: "Where is this specified" gives an objective confirmation that the book is right, which is what the OP (and answers given by other reliable users) implicitly seemed to question IMO. But nevermind, again this is just my opinion on the quality of your answer, I've always trusted its correctness (and upvoted it after the edit). – Andy Prowl Jan 27 '13 at 15:28
  • @andy as much as i am impressed that you think i am an "objective confirmation", i have to say that references to the Standard in answers on SO aren't worth anything. another guy (ironically the first to ask for the location of the rule in the Standard on my answer) posted an hilariously incorrectly quoted part of the Standard as a reply on the same question. – Johannes Schaub - litb Jan 27 '13 at 15:48
  • @JohannesSchaub-litb: there must have been a misunderstanding. I have a *huge* respect for you, but I did not mean to say that you are an "objective confirmation". I meant to say that mentioning the part of the Standard where this rule is stated (and possibly an explanation if the rule is obscure or hard to interpret) gives an objective confirmation. Of course, as you say, one could quote the *wrong* part as well. But that just makes it easier to disprove, as was the case with the incorrect answer. Bringing things down to formal terms always helps disambiguating and understanding. – Andy Prowl Jan 27 '13 at 15:54
  • two standard noobs fighting against each other with random standard quotes doesnt make the result any more objectively confirmed than if there was only one of them. – Johannes Schaub - litb Jan 27 '13 at 16:16
  • @JohannesSchaub-litb: That's arguable: *all* fighting creates confusion, but *formal* fighting is easier to solve. And anyway, since you're far from being a Standard noob, you could have provided that reference. – Andy Prowl Jan 27 '13 at 16:22

2 Answers2

3

Does it mean that a static constexpr variable is not odr-used (and therefore can be in-class initialized) if it is returned from a function?

Yes.

Essentially, as long as you treat it as a value, rather than an object, then it is not odr-used. Consider that if you pasted in the value, the code would function identically- this is when it is treated as an rvalue. But there are some scenarios where it would not.

There are only a few scenarios where lvalue-to-rvalue conversion is not performed on primitives, and that's reference binding, &obj, and probably a couple others, but it's very few. Remember that, if the compiler gives you a const int& referring to period, then you must be able to take it's address, and furthermore, this address must be the same for each TU. That means, in C++'s horrendous TU system, that there must be one explicit definition.

If it is not odr-used, the compiler can make a copy in each TU, or substitute the value, or whatever it wants, and you can't observe the difference.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • I understand the spirit and rationale, +1ed, but I still have some doubt on the technicalities. Does it mean this would be legal: `foo(static_cast(period))`? – Andy Prowl Jan 27 '13 at 14:26
  • Uh, I actually don't know. If lvalue-to-rvalue occurs in that context, and it's fairly obscure so I'm not sure if it does (don't think so, you'll have to check the Standard), then it's legal, if not, then it's not, as an rvalue ref will not bind to a const lvalue. – Puppy Jan 27 '13 at 14:40
  • It seems the output of `static_cast` is a prvalue in this case, which means it is legal. Per 5.2.9/1: *"The result of the expression static_cast(v) is the result of converting the expression v to type T. If T is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue"*. – Andy Prowl Jan 27 '13 at 15:46
  • Here *if T is an rvalue reference to object type, the result is an xvalue;* applies (an int is an object type). An xvalue is a glvalue, not a prvalue, so no lvalue to rvalue conversion takes place. (The point of casting to an rvalue reference is that you can threat the original object as an rvalue without copying the value. – JoergB Jan 27 '13 at 15:51
  • Wrt you first "Yes": the answer is 'no' for a function that returns `int const&` or that returns a class type, which has a converting constructor taking a `int const&` argument. – JoergB Jan 27 '13 at 15:58
  • I'm pretty sure it was implied that it returned by value. But that might just be me. – Puppy Jan 27 '13 at 16:05
  • @DeadMG: yes, I implied return by value. – Andy Prowl Jan 27 '13 at 17:11
3

You missed a part of the premise. The class definition give above is completely valid, if you also define Account::period somewhere (but without providing an initializer). See the last sentance of 9.4.2/3:

The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program and the namespace scope definition shall not contain an initializer.

This entire discussion only applies to static constexpr data members, not to namespace-scope static variables. When static data members are constexpr (rather than simply const) you have to initialize them in the class definition. So your question should really be: What are you allowed to do with a static constexpr data member so that you don't have to provide a definition for it at namespace scope?

From the preceding, the answer is that the member must not be odr-used. And you already found relevant parts of the definition of odr-used. So you can use the member in a context where it is not potentially-evaluated - as an unevaluated operand (for example of sizeofor decltype) or a subexpression thereof. And you can use it in an expression where the lvalue-to-rvalue conversion is immediately applied.

So now we are down to What uses of a variable cause an immediate lvalue to rvalue conversion?

Part of that answer is in §5/8:

Whenever a glvalue expression appears as an operand of an operator that expects a prvalue for that operand, the lvalue-to-rvalue (4.1), array-to-pointer (4.2), or function-to-pointer (4.3) standard conversions are applied to convert the expression to a prvalue.

For arithmetic types that essentially applies to all operators that apply standard arithmetic conversions. So you can use the member in various arithmetic and logical operations without needing a definition.

I can't enumerate all things, you can or can't do here, because the requirements that something be a [g]lvalue or [p]rvalue are spread across the standard. The rule of thumb is: if only the value of the variable is used, a lvalue to rvalue conversion is applied; if the variable is used as an object, it is used as lvalue.

You can't use it in contexts where an lvalue is explicitly required, for example as argument to the address-of operator or mutating operators). Direct binding of lvalue references (without conversion) is such a context.

Some more examples (without detailed standardese analysis):

You can pass it to functions, unless the function parameter is a reference to which the variable can be directly bound.

For returning it from a function: if implicit conversion to the return type involves a user-defined conversion function, the rules for passing to a function apply. Otherwise you can return it, unless the function returns an lvalue (a reference) referring directly to the variable.

The key rule for odr-used variables, the "One Definition Rule" is in 3.2/3:

Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required.

The "no diagnostic required" part means that programs violating this rule cause undefined behavior, which may range from failing to compile, compiling and failing in surprising ways to compiling and acting as if everything was OK. And your compiler need not warn you about the problem.

The reason, as others have already indicated, is that many of these violations would only be detected by a linker. But optimizations may have removed references to objects, so that no cause for linkage failure remains or else linking may sometimes occur only at runtime or be defined to pick an arbitrary instance from multiple definitions of a name.

JoergB
  • 4,383
  • 21
  • 19