Intel vs GCC on constexpr - c++

The following code compiles ok with Intel-2015 but fails with gcc 4.8.3
Who is right?
#include <iostream>
void f( int const& x ) { std::cout << x << std::endl; }
struct S
{
static constexpr int ce = 42;
};
int main()
{
f(S::ce);
return 0;
}
g++ error:
/tmp/ccOIxa2V.o: In function `main':
test_constexpr.cpp:(.text+0x36): undefined reference to `S::ce'
collect2: error: ld returned 1 exit status

Because the function f takes a reference argument, there has to be a definition of S::ce that a reference can point to at runtime; the compiler can't just replace the argument with a literal 42. So you have to add an out-of-class definition:
const int S::ce;
just like you would with a non-constexpr variable. This allocates a memory location for the value at runtime, for use by references and other things that couldn't be computed at compile time.
See this GCC bug report (which has comments explaining why it isn't actually a bug) for more information.

I think GCC is right. BTW, CLang 3.5.1 gives the same error.
The thing is that constant static variables are allowed not to be defined only if their address is not taken and they are not bound to references.
Your example bounds a reference to it, so the explicit definition is needed.
From the C++11 draft (9.4.2.3), conveniently edited:
A static data member of literal type can be declared in the class definition with the constexpr specifier; [...] The member shall still be defined in a namespace scope if it is odr-used (3.2) in the program [...].
And in 3.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.
That lvalue-to-rvalue conversion stuff is, roughly speaking, done everytime the variable is used except when binding a reference or as argument to the unary take-address operator &.

Related

Non-literal types and constant expressions

struct A {
~A() {}
consteval A() {}
consteval auto f() {}
};
int main() {
A{};
//A{}.f(); //1
}
https://godbolt.org/z/4KPY5P7o7
This program is accepted by ICC, GCC and Clang, but rejected by MSVC which complains that the destructor is not constexpr in the immediate function invocation.
Adding the line marked //1 results in all four compilers rejecting the code.
Question: In either case, are the compilers correct, and if so why?
Note that the interesting part here is that A is non-literal due to the non-constexpr non-trivial destructor. Removing its declaration, all compilers accept both the variant with and without //1.
There are a few restrictions specific to non-literal types for constexpr/consteval functions and for constant expressions, but I don't think any of them should apply here. The restrictions are on return types, parameter types, types of local variable definitions, rvalue-to-lvalue conversions and modifications of objects. I think only the last one can apply here. But what exactly does modification in [expr.const]/5.16 mean and which object would be modified here?
I also think MSVC's complaint is incorrect since the destruction of the object shouldn't be part of its constructor's immediate invocation.
See also my earlier question inspiring this one: Consteval constructor and member function calls in constexpr functions
Updated with more exact references to the standard:
The pieces I found relevant (links are from the N4868 draft found here):
An immediate invocation is a full-expression [expr.const]
"Temporary objects are destroyed as the last step in evaluating the full-expression ([intro.execution]) that (lexically) contains the point where they were created. ... The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression." [class.temporary]
"The argument list is the expression-list in the call augmented by the addition of the left operand of the . operator in the normalized member function call as the implied object argument ([over.match.funcs])." [over.call.func]
"A constant expression is either a glvalue core constant expression that ..., or a prvalue core constant expression whose ..." [expr.const]
"An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following: ... an invocation of a non-constexpr function;" [expr.const]
"An immediate invocation shall be a constant expression." [expr.const]
"An object or reference is usable in constant expressions if it is ... a temporary object of non-volatile const-qualified literal type whose lifetime is extended ([class.temporary]) to that of a variable that is usable in constant expressions," [expr.const]
"A type is a literal type if it is: ... a possibly cv-qualified class type that has all of the following properties: it has a constexpr destructor ([dcl.constexpr])," [basic.types]
Consider the following example:
struct A {
~A() {} // not constexpr
consteval int f() { return 1; }
};
template<class T>
consteval int f(T&& a) { return sizeof(a); }
consteval int f(int x) { return x; }
void g() {}
int main() {
A a;
f(a); // ok
a.f(); // ok
f(a.f()); // ok
f(sizeof(A{})); // ok
f(A{}); // not ok TYPE 1 (msvc) or TYPE 2 (clang, gcc)
A{}.f(); // not ok TYPE 1 (msvc) or TYPE 2 (clang, gcc)
f((A{},2)); // not ok TYPE 1 (clang, msvc) or TYPE 2 (gcc)
f((g(),2)); // not ok TYPE 1 (clang, gcc, icc, msvc)
}
Error diagnostics are about violating that immediate invocations should be constant expressions.
// msvc:
error C7595: 'f' ((or 'A::f')): call to immediate function is not a constant expression
// icc:
call to consteval function "f(T&&) [with T=A]" ((or "A::f" or "f(int)")) did not produce a valid constant expression
// clang:
error: call to consteval function 'f<A>' ((or 'A::f' or 'f')) is not a constant expression
Note that gcc does not mention the violation of this consteval/immediate function specific rule explicitly.
For the temporaries we receive two types of diagnostics from different compilers.
Some see the problem in calling a non-constexpr destructor or function in a constant (full-)expression. TYPE 1:
// msvc:
note: failure was caused by call of undefined function or one not declared 'constexpr'
note: see usage of 'A::~A' ((or 'g'))
// icc:
note: cannot call non-constexpr function "g"
// gcc:
error: call to non-'constexpr' function 'void g()'
// clang:
note: non-constexpr function '~A' ((or 'g')) cannot be used in a constant expression
Others (except for icc, which is silent about it) highlight that non-literal type temporaries cannot be present in constant expressions. TYPE 2:
// gcc:
error: temporary of non-literal type 'A' in a constant expression
note: 'A' is not literal because:
note: 'A' does not have 'constexpr' destructor
// clang:
note: non-literal type 'A' cannot be used in a constant expression
I think for consteval consideration A{}.f() is equivalent to the f(A{}) case because of the implicit object parameter of A::f.
The surprising observation from Fedor that icc compiles A{A{}}.f() is true even if A::A(const A&) is implemented to call e.g. printf. The code compiles, but outputs nothing. I consider that a bug.
Interestingly icc generates an error for the semantically very similar f(A{A{}}) variant.
My original post for reference (helps understanding some of the comments) :
For me the output diagnostics make sense.
My mental model about immediate invocations is this: you are allowed to use an immediate function only within immediate contexts.
An expression that contains anything else than constexpr operations is not an immediate context.
In your example the expression is not only an invocation of the constexpr constructor, but because the temporary is part of the expression, its destruction should also happen as part of the evaluation of the expression. Therefore your expression is no longer an immediate context.
I was playing around just calling the constructor with placement new to avoid the dtor call being part of the expression, but placement new itself is not considered constexpr either. Which is, I think, conceptually best explained by pointers should not present in immediate contexts at all.
If you remove ctor/dtor from the expression:
A a;
a.f();
then it compiles fine.
An interesting bug in ICC that it fails to compile A{}.f() even with a constexpr dtor, and you cannot convince it no matter how trivial definition your f has:
error: call to consteval function "A::f" did not produce a valid constant expression
A{}.f();
^
while it compiles the simple a.f() variant listed above without any complaint.

Private static array in C++ [duplicate]

This is a follow up question to Undefined reference to static constexpr char[][].
The following program builds and runs fine.
#include <iostream>
struct A {
constexpr static char dict[] = "test";
void print() {
std::cout << A::dict[0] << std::endl;
}
};
int main() {
A a;
a.print();
return 0;
}
However, if I change A::print() to:
void print() {
std::cout << A::dict << std::endl;
}
I get the following linker error in g++ 4.8.2.
/tmp/cczmF84A.o: In function `A::print()':
socc.cc:(.text._ZN1A5printEv[_ZN1A5printEv]+0xd): undefined reference to `A::dict'
collect2: error: ld returned 1 exit status
The linker error can be resolved by adding a line:
constexpr char A::dict[];
outside the class definition.
However, it's not clear to me why using one of the members of the array does not cause a linker error while using the array causes a linker error.
The standard does not require any diagnostics for a failure to provide a definition where one is required.
3.2 One definition rule [basic.def.odr]
4 Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required. [...]
This means implementations are allowed to optimise away accesses to such variables, and that's what's happening in your first case with GCC.
Both GCC and clang have decided that they prefer a consistent user experience, where error messages about missing definitions do not depend on the optimisation level. Usually, that means that any missing definition causes an error message. However, in this case, GCC is doing some minimal optimisation even at -O0, avoiding the error.
But the program is an error either way, because even A::dict[0] is an ODR-use:
3.2 One definition rule [basic.def.odr]
3 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.19) 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, or e is a discarded-value expression (Clause 5). [...]
The use of A::dict doesn't involve lvalue-to-rvalue conversion, it involves the array-to-pointer conversion, so the exception doesn't apply.
In addition to the information provided by #hvd in his answer...
From the C++ Draft Standard N3337 (emphasis mine):
9.4.2 Static data members
3 If a non-volatile const static data member is of integral or enumeration type, its declaration in the class definition can specify a brace-or-equal-initializer in which every initializer-clause that is an assignment- expression is a constant expression (5.19). A static data member of literal type can be declared in the class definition with the constexpr specifier; if so, its declaration shall specify a brace-or-equal-initializer in which every initializer-clause that is an assignment-expression is a constant expression. [ Note: In both these cases, the member may appear in constant expressions. — end note ] 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.
Given that A::data is odr-used in the expression A::data[0], as per the standard, it shall be defined in a namespace scope. The fact that g++ is able to successfully create a program without A::data being defined in a namescpace scope does not make the program right. To be standards compliant, A::data shall be defined.

Are variables appearing in function expression taking arguments by reference but returning by value odr-used?

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?
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.
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...

Calling a constexpr method through a reference - is the result a constant expression?

The following code
#include <array>
void foo(const std::array<int, 42> &a)
{
constexpr size_t S = a.size();
}
int main() {}
compiles fine in GCC, but fails to compile in clang with the following error message
main.cpp:5:28: error: constexpr variable 'S' must be initialized by a constant expression
constexpr size_t S = a.size();
^~~~~~~~
Meanwhile, many posts about constexpr issues on SO seem to imply that clang often has better (more pedantic?) support for constexpr. So, which compiler would be correct in this case?
Note that both compilers gladly accept the code once the reference parameter is replaced with pass-by-value parameter.
[expr.const]/2:
A conditional-expression e is a core constant expression unless
the evaluation of e, following the rules of the abstract machine
([intro.execution]), would evaluate one of the following expressions:
[...]
an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either
it is initialized with a constant expression or
its lifetime began within the evaluation of e;
[...]
Evaluating a.size() evaluates the id-expression a, which "refers to a variable...of reference type" and has no preceding initialization. It is therefore not a core constant expression and so not a constant expression.

template instantiation with constexpr function failure

I have template class C that has a non-type but reference template parameter to a type P:
class P {
public:
int x;
int y;
};
template <const P &x>
class C {
public:
const int &f() { return x.x; }
};
I declared a global variable of type P:
P p = {33,44};
I also declared a function that returns a reference to p:
constexpr const P &h() { return p; }
And then tried to use these in the following :
C<p> o; // line 1
C<h()> oo; // line 2
Of course I have no problem with the first instantiation but the second. My compiler complains:
error: non-type template argument does not refer to any declaration
Why is so ? I was unable to find an argument against it in the norm. I am not sure that it is exactly the same problem as in Calling constexpr in default template argument, where the discussion was about point of instantiation of nested instanciation. Here it is more a type problem, but which one ? My function h() returns a reference to a well defined variable of the well defined type (const P &). I expected that some inlining would take place a give the right result but it is not the case. Could you tell me why ?
Declaring the function as inline doesn't change anything to the problem.
Experiments were done with Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn. I also tried with g++-mp-4.8 (MacPorts gcc48 4.8.3_2) 4.8.3 and the error was reported as:
'h()' is not a valid template argument for type 'const P&' because it is not an object with external linkage
It looks like my call to h() (which is a constexpr so compile-time computable) is not seen as such...
I forgot to say that the problem is the same if we try with another reference like this:
const P &pp = p;
and then
C<pp> oo;
this time the first compiler says:
non-type template argument of reference type 'const P &' is not an object
and the second:
error: could not convert template argument 'pp' to 'const P &'
pp is not an object? pp is not of type const P&? Well I can use it as is it one... I know it is a reference but indistinguishable from a native reference, or ?
It looks like this restriction was subject to the following proposal Allow constant evaluation for all non-type template arguments, still trying to determine the status of this proposal. It says:
The syntactic restrictions for pointers, references, and pointers to
members are awkward and prevent reasonable refactorings. For instance:
template<int *p> struct A {};
int n;
A<&n> a; // ok
constexpr int *p() { return &n; }
A<p()> b; // error
and further says:
The historical reason for the restriction was most likely that C++
previously did not have a sufficiently strong specification for
constant expressions of pointer, reference, or pointer-to-member type.
However, that is no longer the case. The status quo is that an
implementation is required to evaluate such a template argument, but
must then discard the result if it turns out to not be null.
In addition to the above, the restriction to entities with linkage is
an artifact of exported templates, and could have been removed when
the linkage restrictions on template type parameters were removed.
and it would remove this section of the note with this restriction:
unnamed lvalues, and named lvalues with no linkage
the whole note reads:
Temporaries, unnamed lvalues, and named lvalues with no linkage are
not acceptable template-arguments when the corresponding
template-parameter has reference type.
Update
The revised version of this proposal N4268 was adopted into the working draft at Urbana and we can see the changes in the latest working draft N4296. The new note reads:
A temporary object is not an acceptable template-argument when the
corresponding template-parameter has reference type
The normative section is 14.3.2 (temp.arg.nontype) paragraph 1 which with this proposal would say:
For a non-type template-parameter of reference or pointer type, the
value of the constant expression shall not refer to (or for a pointer
type, shall not be the address of):
a subobject (1.8),
a temporary object (12.2),
a string literal (2.14.5),
the result of a typeid expression (5.2.8), or
a predefined func variable (8.4.1).
and we can find this new wording in the latest draft standard N4296.
It looks like this change has actually been implemented in clang HEAD see your code working live, using the -std=c++1z flag. This implies the change should be part of C++17, assuming no subsequent changes reverses or alters it.
In the case of
C<h()> oo;
§14.3.2/4 kicks in:
[Note: Temporaries, unnamed lvalues, and named lvalues with no linkage are not acceptable template-
arguments when the corresponding template-parameter has reference type.
(emphasis mine)