3
votes

Given the code snippet:

struct S {
    static const int var = 0; 
}; 

int function(const int& rVar){
    return rVar; 
}

int main()
{
    return function(S::var); 
}

Compiled with gcc 5.4.0:

g++ -std=c++17 main.cpp -o test

results in the following linkage error:

/tmp/ccSeEuha.o: In function `main': main.cpp:(.text+0x15): undefined reference to `S::var' collect2: error: ld returned 1 exit status

§3.3 from the ISO Standard C++17 draft n4296 states:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.20) 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 (4.1) is applied to e [bold-type formatting added], or e is a discarded-value expression (Clause 5).

Q: Why is a definition of the variable var required here? Isn't var denoting an integer object that appears in the potentially-evaluted expression S::var of an outter function call expression, which indeed takes a parameter by reference, but to which finally a lvalue-to-rvalue conversion is applied, and, thus isn't odr-used as stated in the paragraph?

2
I don't think a lvalue-to-rvalue conversion is applied in reference initialization. - cpplearner
@user2079303 Turn off -O2. - cpplearner
@cpplearner I see. Damn UB :) - eerorika

2 Answers

5
votes

but to which finally a lvalue-to-rvalue conversion is applied, and, thus isn't odr-used as stated in the paragraph?

The lvalue-to-rvalue conversion in the other expression is irrelevant I believe. There is no lvalue-to-rvalue conversion applied to subexpression S::var in the expression function(S::var), thus the exception does not apply.


Considering from the common sense point of view, rather than analysing the rule: functioncould be defined in another translation unit, so the compiler cannot necessarily know how the reference would be used. It cannot just send a copy of the value to the function and hope that the function definition won't use the object in a way that would require the definition of the referred object. Likewise, when compiling the function, the compiler cannot assume that all function calls would send anything other than a reference to an object that exists.

Technically, I suppose that there could be yet more complicated exception for reference arguments of inline functions, but there isn't such exception in the standard. And there shouldn't be since it would make inline expansion mandatory in those cases. In practice, a compiler might behave exactly as such exception would require if it happens to expand the function inline, since odr violations have undefined behaviour.

1
votes

It is explicit in the example show just above the paragraph you quoted: in function(S::var);, S::var is odr-used.

The reason is that as function takes its parameter by ref (and not by value), no lvalue to rvalue conversion occurs.

But if you change function to take its argument by value:

int function(const int rVar){
    return rVar; 
}

then the lvalue to rvalue conversion occurs, and S::var is non longer odr-used. And the program no longer exhibit the undefined reference...