11

The code below compiles in GCC, clang and VS2017 and the expression a->i in the return statement is replaced by its constant value 1. Is it correct to say that this is valid because a is not odr-used in the expression a->i?.

struct A 
{ 
    static const int i = 1; 
}; 
int f() 
{ 
    A *a = nullptr; 
    return a->i;
}

PS: I believe a is not odr-used in the expression a->i because it satisfies the "unless" condition in [basic.def.odr]/4, as follows:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.6) that does not invoke any non-trivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (7.1) is applied to e, or e is a discarded-value expression (8.2).

In particular, the expression ex == a is an element of the set of potential results of the expression e == a->i, according to [basic.def.odr]/2 (2.3), containing the expression ex, where the lvalue-to-rvalue conversion is applied to e.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Ayrosa
  • 3,385
  • 1
  • 18
  • 29
  • It's a good question, well-formulated and written (so +1). But *why* would anyone do anything like that? – Some programmer dude May 19 '18 at 18:34
  • I can't believe this compiles and appears in order. Looking forward to an explanation from a smarter human. – DeiDei May 19 '18 at 18:40
  • @Someprogrammerdude I got this example from [this discussion](https://groups.google.com/a/isocpp.org/d/msg/std-discussion/zmRxdNdVUNI/a9w5vAb-BAAJ) in C++ std-discussion, but I think the OP was incorrect when he said that the variable `s` is odr-used in his example, equivalent to the one given above. – Ayrosa May 19 '18 at 18:42
  • 1
    Aren't you forgetting that it says "yields a constant expression [...] and [what you wrote]"? `a` is not a constant expression. – Rakete1111 May 19 '18 at 18:44

2 Answers2

13

a is odr-used because you fail the first part of the "unless":

applying the lvalue-to-rvalue conversion (7.1) to x yields a constant expression (8.6) that does not invoke any non-trivial functions

Applying the lvalue-to-rvalue conversion to a does not yield a constant expression.

The rest is core issues 315 and 232.


Your analysis is broken in two additional ways:

  • The "object expression" is defined using the . form of class member access, so you need to rewrite a->i to dot form, i.e., (*a).i, before applying [basic.def.odr]/2.3. a is not a member of the set of potential results of that expression.
  • That bullet itself is defective because it was written with non-static data members in mind. For static data members, the set of potential results should be in fact the named static data member - see core issue 2353, so a is doubly not a member of the set of potential results of that expression.

[expr.const]/2.7:

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine, would evaluate one of the following expressions:

  • [...]
  • an lvalue-to-rvalue conversion unless it is applied to
    • a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
    • a non-volatile glvalue that refers to a subobject of a string literal, or
    • a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
    • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
  • [...]
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Isn't `nullptr` a constant expression? – Ayrosa May 19 '18 at 18:50
  • 2
    @Ayrosa That doesn't make `a` a constant expression. `int b = 1;` is `b` here a constant expression because `1` is? – Rakete1111 May 19 '18 at 18:53
  • @Rakete1111 The quoted text doesn't say that `a` **must be** a constant expression. It says that `a` yields a constant expression, after an l-t-r conversion. AFAICT `a` yields a constant expression, `nullptr`. – Ayrosa May 19 '18 at 18:57
  • 1
    @Ayrosa An l-t-r conv on `a` doesn't yield `nullptr`, it yields an rvalue that has the value `nullptr`. That rvalue is not a constant expression. – Rakete1111 May 19 '18 at 19:03
  • @T.C. You are trying to imply that `nullptr` is not a constant expression using [\[expr.const\]/2.7](http://eel.is/c++draft/expr.const#2.7), as if a `nullptr` were subjected to an l-t-r conversion. But that is not correct, as `nullptr` is a prvalue, as can be seen [here](http://eel.is/c++draft/lex.nullptr#1). – Ayrosa May 19 '18 at 20:20
  • 1
    @Ayrosa You are asking about the odr-use of `a`. And the quote at issue means that you ask whether applying l-t-r to `a` yields a constant expression. It doesn't. – T.C. May 19 '18 at 20:25
  • @T.C. It does. I'm not saying and I don't need to say that `a` is a constant expression. Of course, it is not. What I'm saying is that `a` after an l-t-r conversion, it yields a constant expression, `nullptr`. And `nullptr`is a core constant expression, because it doesn't evaluate none of the expressions in [expr.const]/2.7. – Ayrosa May 19 '18 at 20:29
  • 5
    You have a *very* weird definition of "yields" that somehow manages to skip over the fact that the act of applying the l-to-r conversion to `a` cannot occur in a constant expression. (Besides, the result of applying l-to-r to `a` is not `nullptr`; it's a null pointer value of type `A*`.) – T.C. May 19 '18 at 20:35
  • @T.C." the act of applying the l-to-r conversion to `a` cannot occur in a constant expression" - this doesn't make any sense to me. – Ayrosa May 19 '18 at 20:40
  • 1
    @Ayrosa You are arguing this `int i = 42; int arr[i];` – Passer By May 20 '18 at 08:28
  • @T.C. You're saying that `a` is odr-used because `a` is not a constant expression. That doesn't make sense (-1). – Alexander May 20 '18 at 11:57
  • 4
    @Alexander That makes perfect sense and you can see that in real compilers today. Given `struct S { static const int i = 0; };`, the fact that `int main() { const int *pi = &S::i; return *pi; }` doesn't work (without optimisations) shows that this doesn't provide a definition for `S::i`, but `int main() { return S::i; }` still works, and is guaranteed to work. To allow this, the compiler effectively must replace `S::i` by a literal `0`, avoiding the need for a definition. it can only do so for constant expressions. For non-constant expressions, it needs the definition to actually load a value. –  May 23 '18 at 07:44
  • I finally came to realize that your answer was correct. Thanks (+1). – Ayrosa Jan 14 '19 at 12:52
-2

i is a static member of the class... as you can access the static members of the class through using the conventional methods for the instances, they are not tied to any instance in particular, so you don't need to dereference the nullptr pointer (as when you use the sizeof operator). You could also access the field using a simple

return A::i;

statement, because you don't need to create an instance to access it. Indeed, being const, the compiler allows to manage it as a constant value, so only in the case you need to use it's address (by means of the & operator) the compiler can bypass allocating it in read-only memory.

The following sample will probe that:

#include <iostream>

struct A { 
    static const int i = 1; 
}; 

int main()
{
    std::cout << ((A*)0)->i << std::endl;
    std::cout << A::i << std::endl;
}

will print

$ a.out
1
1
$ _
Robert Andrzejuk
  • 5,076
  • 2
  • 22
  • 31
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31
  • Thanks @RobertAndrzejuk, for the formatting.... I forgot to do it before posting. Don't know if the formatting was the reason for a downvote, but a comment on the reason for downvote will be appreciated. – Luis Colorado May 23 '18 at 07:50
  • The downvote should be used as a way to improve responses, not to compete for reputation by downvoting the others.... The response is valid and probes the response with a working, complete, and verifiable example, things that the original question don't even do. Let's give a chance for the downvoter to explain himself. – Luis Colorado May 23 '18 at 08:17
  • The obvious flaw in this answer is that it doesn't answer the question. This post is marked [tag:language-lawyer]. It's about the intricacies of the language specification itself. So an answer that doesn't even references the standard is not useful. Voting reflects that (as the help text on the downvote button specifies). Which is why I also object to the unfounded assumption of "competing for rep". Your time would be better spent reviewing your own post with the rigor this question demands instead of postulating foul play on the part of others. And just so we are clear, it's my vote. – StoryTeller - Unslander Monica May 23 '18 at 10:08
  • @StoryTeller Thanks for the explanation. Now this downvote has some merit. A downvote by itself is non constructive - it's like receiving the silent treatment, because "You know what you did! Now fix it!" But actually people don't know. – Robert Andrzejuk May 23 '18 at 12:17
  • @StoryTeller, if this question is marked language-lawyer as you said, then it would be better use an example that doesn't include `static` members and shows the same behaviour, because, as written, it only shows that the requestor doesn't distinguish between access modes to static members of a class (as global variables, not requiring an instance) and instance fields. Of course it does answer the question, but probably the question is what must be objected, and not by marking it language-lawyer get the credit of beint untouchable. My response included tested examples, just for you to downvot – Luis Colorado Jun 06 '18 at 11:38