GCC 4.7.2 compiles this:
constexpr int i = 5;
[]{ std::integral_constant< int, i >(); }; // nonstandard: i not captured
but not this:
constexpr int i = 5;
[&i]{ std::integral_constant< int, i >(); }; // GCC says i not constexpr
The latter example appears correct to me, according to C++11 §5.1.2/15:
An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.
It seems the captured object i inside the lambda refers to the variable in the enclosing scope, which is constexpr, not merely a const reference.
The standard explicitly says that the use of a by-value capture is transformed into a use of the corresponding member of the lambda object. And I think that 5.1.2 hints that my interpretation is correct.
Is there anything that explicitly says that whether a capture by reference refers to the object in the enclosing scope or a reference?
The second template-argument to std::integral_constant< int, i > is for a template-parameter of non-type form, specifically of integral or enumeration type (14.3.2p1 bullet 1) and so must be a converted constant expression of type int.
In a lambda-expression, implicit capture occurs when an entity is odr-used in the compound statement (5.1.2p11); use of a converted constant expression in an explicit template instantiation is not odr-use (3.2p3), so the first example is valid.
In the second example, I think gcc is incorrect to reject it; 5.1.2p17 says in a note that:
An id-expression that is not an odr-use refers to the original entity, never to a member of the closure type.
Although the paragraph as a whole is discussing capture by copy, there's no reason not to apply this rule to capture by reference as well. It's unsurprising that the standard is unclear on this; there's really no reason to capture an entity that can be used in a converted constant expression by reference.
First, I can confirm your observation with gcc 4.6.3 and clang 3.0 on Ubuntu 12.04.
I don't have the C++11 standard (only draft), so I cannot comment on that. But look at the, from my understanding, equivalent statements
constexpr int i = 5;
const int &j = i;
std::integral_constant<int, j>();
Neither gcc, nor clang compiles this, because j is not an "integral constant".
Related
The following program
#include <type_traits>
struct S {
void f() const {
(void)[*this]() mutable {
static_assert(
std::is_same_v<decltype(this), S *>); // Fails with GCC & MSVC
static_assert(
std::is_same_v<decltype(this), const S *>); // Fails with Clang
(void)this;
}
();
}
};
int main() {}
contains a mutable lambda which captures *this by value ([*this]), and which does so in a const-qualified member function. The program is rejected by GCC due to #1, and rejected by Clang due to #2 (and conversely accepted):
GCC(1) and MSVC thinks decltype(this) is const S *
Clang(1) thinks decltype(this) is S *
(1) Various compiler versions for C++17 and C++20
DEMO.
Which compiler is right here?
GCC/MSVC are correct, Clang is reasonable but incorrect
[expr.prim.lambda.closure[/12:
The lambda-expression's compound-statement yields the function-body ([dcl.fct.def]) of the function call operator, but for purposes of name lookup, determining the type and value of this and transforming id-expressions referring to non-static class members into class member access expressions using (*this) ([class.mfct.non-static]), the compound-statement is considered in the context of the lambda-expression.
means [class.this]/1 applies:
In the body of a non-static ([class.mfct]) member function, the keyword this is a prvalue whose value is a pointer to the object for which the function is called. The type of this in a member function whose type has a cv-qualifier-seq cv and whose class is X is “pointer to cv X”.
GCC/MSVC and Clang may be applying [class.this]/1, particularly regarding the cv-qualifier of the member function, to either of
the cv-qualification of the member function in which the lambda is defined, S::f() in OP's example, which is const-qualified, or
the cv-qualification of the function call operator of the lambda, which is not const-qualified (due to mutable),
particularly GCC and MSVC choosing interpretation 1, whereas Clang chooses interpretation 2. We may note that if we slightly tweak OP's example to capture the lambda by capturing *this by reference, [this], all compilers agree that decltype(this) in the lambda is const S * (which makes sense).
Based on the standard passages above, particularly [expr.prim.lambda.closure]/12, I would argue that Clang is wrong to let this be non-const-qualified when *this has been value-captured by a mutable lambda from within a const-qualified method. Even if it could make sense to base constness for a value-copied *this on the constness of the function call operator of the lambda's closure type, the standard does not support it.
We may finally note that we see the same behaviour if using overload resolution instead of decltype (i.e. comparing this in our outside of an unevaluated context), namely GCC and MSVC preserving constness of the copy-captured *this, where Clang does not.
Bug reports?
I have not been able to find any Clang bug reports for this, and have hence filed:
Bug 51524 - '*this' captured by copy does not preserve constness of '*this' in mutable lambda
I would want to use a constexpr value in a lambda. Reading the answer to
Using lambda captured constexpr value as an array dimension, I assumed the following should work:
#include<array>
int main()
{
constexpr int i = 0;
auto f = []{
std::array<int, i> a;
};
return 0;
}
However, Clang 3.8 (with std=c++14) complains that
variable 'i' cannot be implicitly captured in a lambda with no
capture-default specified
Should this be considered a bug in clang 3.8?
BTW:
The above code does compile with gcc 4.9.2.
If I change the lambda expresion to capture explicitly:
...
auto f = [i]{
...
clang 3.8 compiles it, but gcc 4.9.2 fails:
error: the value of ‘i’ is not usable in a constant expression
...
Should this be considered a bug in clang 3.8?
Yep. A capture is only needed if [expr.prim.lambda]/12 mandates so:
Note in particular the highlighted example. f(x) does not necessitate x to be captured, because it isn't odr-used (overload resolution selects the overload with the object parameter). The same argumentation applies to your code - [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.20) that does not invoke
any non-trivial functions…
This requirement is certainly met.
…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).
i is its set of potential results as per [basic.def.odr]/(2.1), and the l-t-r conversion is indeed immediately applied as its passed to a non-type template parameter of object type.
Hence, as we have shown that (12.1) isn't applicable - and (12.2) clearly isn't, either - Clang is wrong in rejecting your snippet.
Is it permitted to declare a non-const reference as constexpr? Example code:
int x = 1;
constexpr int& r = x;
This is accepted by gcc and clang (I tried several current and past versions of both, back to C++11, and all accepted it). However I think it should not be accepted because C++14 [dcl.constexpr/9] says:
if a constexpr specifier is used in a reference declaration, every full-
expression that appears in its initializer shall be a constant expression
and x is not a constant expression.
The language in the latest C++17 draft of [dcl.constexpr] changed and doesn't even mention constexpr references explicitly any more, I can't make head nor tail of what it is trying to say about them.
Assuming that x has static storage duration, the lvalue expression x is a perfectly valid constant expression.
If you use x in a context that requires a prvalue, which causes the lvalue-to-rvalue conversion to be applied to it, then the resulting prvalue expression - call it TO_RVALUE(x) - would not be a constant expression, for obvious reasons. But in the case of reference binding, there is no such conversion.
I would want to use a constexpr value in a lambda. Reading the answer to
Using lambda captured constexpr value as an array dimension, I assumed the following should work:
#include<array>
int main()
{
constexpr int i = 0;
auto f = []{
std::array<int, i> a;
};
return 0;
}
However, Clang 3.8 (with std=c++14) complains that
variable 'i' cannot be implicitly captured in a lambda with no
capture-default specified
Should this be considered a bug in clang 3.8?
BTW:
The above code does compile with gcc 4.9.2.
If I change the lambda expresion to capture explicitly:
...
auto f = [i]{
...
clang 3.8 compiles it, but gcc 4.9.2 fails:
error: the value of ‘i’ is not usable in a constant expression
...
Should this be considered a bug in clang 3.8?
Yep. A capture is only needed if [expr.prim.lambda]/12 mandates so:
Note in particular the highlighted example. f(x) does not necessitate x to be captured, because it isn't odr-used (overload resolution selects the overload with the object parameter). The same argumentation applies to your code - [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.20) that does not invoke
any non-trivial functions…
This requirement is certainly met.
…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).
i is its set of potential results as per [basic.def.odr]/(2.1), and the l-t-r conversion is indeed immediately applied as its passed to a non-type template parameter of object type.
Hence, as we have shown that (12.1) isn't applicable - and (12.2) clearly isn't, either - Clang is wrong in rejecting your snippet.
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)