[expr.prim.lambda.capture]/7:
If an expression potentially references a local entity within a scope in which it is odr-usable, and the expression would be potentially evaluated if the effect of any enclosing typeid expressions ([expr.typeid]) were ignored, the entity is said to be implicitly captured by each intervening lambda-expression with an associated capture-default that does not explicitly capture it. The implicit capture of *this is deprecated when the capture-default is =; see [depr.capture.this]. [Example 4:
void f(int, const int (&)[2] = {});
void test() {
const int x = 17;
auto g = [](auto a) {
f(x); // OK, calls #1, does not capture x
};
auto g1 = [=](auto a) {
f(x); // OK, calls #1, captures x
};
}
... Within g1, an implementation can optimize away the capture of x as it is not odr-used. — end example]
So an entity is captured even if it is not odr-used by the lambda body, and the example below says that the implementations can optimize it away. Therefore, since the implementations can optimize it away, why add such a rule? What's the point of it?
cppreference says it was added in C++17. What's the original proposal?
P0588R0 contains the rationale explaining why the rules for implicit capture were changed in order to capture some variables that are not going to be odr-used anyway. It's very subtle.
Basically, in order to determine the size of a lambda closure type, you need to know which variables are captured and which ones are not, but under the old rules:
in order to determine whether a variable is captured implicitly, you have to know whether it's odr-used, and
in order to know whether the variable is odr-used, you have to perform substitution into the lambda's body, and
if the function call operator is generic, it means the above substitution is done at a point where the function call operator itself is not ready to be instantiated yet (since its own template parameters aren't yet known). This causes problems that could be avoided by not doing the substitution in the first place.
The paper gives the following example:
template<typename T> void f(T t) {
auto lambda = [&](auto a) {
if constexpr (Copyable<T>() && sizeof(a) == 32) {
T u = t;
} else {
// ... do not use t ...
}
};
// ...
}
When f is instantiated with a non-copyable type, ideally we would like the branch with T u = t; to be discarded. That's what the if constexpr is there for. But an if constexpr statement does not discard the un-taken branch until the point at which the condition is no longer dependent---meaning that the type of a must be known before T u = t; can be discarded. Unfortunately, under the old rules, T must be substituted into T u = t; in order to determine whether t is captured, and this happens before any opportunity to discard this statement
The new rules simply declare that t is captured; therefore, the compiler doesn't have to perform any substitution into the lambda body until the point at which a specialization of the function call operator is referenced (and thus, its body can be fully instantiated). And if the compiler is somehow able to prove that t will never be odr-used by any possible specialization of the function call operator, it's free to optimize it out.
Related
For example:
void foo()
{
if constexpr (...)
int x = 5;
else
double x = 10.0;
bar(x); // calls different overloads of bar with different values
}
It's common case in D lang, but I didn't found info about C++17.
Of course, it is possible to use something like
std::conditional<..., int, double>::type x;
but only in elementary cases. Even different initializators (as above) creates big problem.
There is no way this code could work. The problem is that x is out of scope when you are calling bar. But there is a workaround:
constexpr auto t = []() -> auto {
if constexpr(/* condition */) return 1;
else return 2.9;
}();
bar(t);
To explain a little bit, it uses instantly invoked lambda expression along with auto return type deduction. Therefore we are giving t value in place and it does not get out of the scope.
Of course, it wouldn't work if the if statement couldn't have been evaluated at compile time. And if you want to do some runtime operations inside of this lambda you cannot have t as constexpr, but it will still work.
There are two ways this won't work.
First, a variable is limited to the scope in which it's declared. Leaving out the braces isn't gonna fool the compiler: int x = 5 is still in its own scope, and disappears immediately after it appears.
Second, the relaxed grammar rules for if constexpr only apply within the if constexpr's body. It would be infeasible to allow the context created in the body to leak out to the surrounding scope, since by definition it may not be well-formed, or consistent between the then/else block. (What if the else-block declared x as a typename?)
Bottom line, you'll need to move bar() into the if-body, or template foo() itself and have the type and value of x determined by your ....
#include <variant>
struct S {
constexpr auto f() -> void {
// deleting the next line creates an error
if(std::holds_alternative<int>(m_var))
m_var.emplace<double>(5.0);
}
std::variant<int, double> m_var;
};
int main() {
return 0;
}
std::variant has a non-constexpr member function emplace(). In general you can't use that in constexpr functions. You can however if you surround that call by a condition that uses std::holds_alternative() on that type. Also other constexpr functions as long as they're member functions in that class.
I'm having trouble to understand what' going on. My first reaction was to say that's a bug. That condition can't possibly be more constexpr than no condition at all. But maybe that was premature. Can anyone shed some light on this? Why is it that emplace() is not constexpr but (equal-type) assignments are?
Edit: Maybe to expand a bit: One guess is that constructors and destructors of the involved variants could be non-constexpr and that's why emplace etc are not. But the fun thing is that you can use conditions like this to compile the function as constexpr even when you explicitly abuse a non-constexpr constructor. That voids that argument.
godbolt: here.
You don't actually need to delve much into std::variant to reason about this. This is mostly about how constant expressions work. constexpr functions must be defined in a way that allows for evaluation in a constant expression. It doesn't matter if for some arguments we run into something that can't appear in a constant expression, so long as for other arguments we obtain a valid constant expression. This is mentioned explicitly in the standard, with an exeample
[dcl.constexpr]
5 For a constexpr function or constexpr constructor that is
neither defaulted nor a template, if no argument values exist such
that an invocation of the function or constructor could be an
evaluated subexpression of a core constant expression, or, for a
constructor, a constant initializer for some object
([basic.start.static]), the program is ill-formed, no diagnostic
required. [ Example:
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
struct B {
constexpr B(int x) : i(0) { } // x is unused
int i;
};
int global;
struct D : B {
constexpr D() : B(global) { } // ill-formed, no diagnostic required
// lvalue-to-rvalue conversion on non-constant global
};
— end example ]
See how f(bool) is a valid constexpr function? Even though a throw expression may not be evaluated in a constant expression, it can still appear in a constexpr function. It's no problem so long as constant evaluation doesn't reach it.
If there is no set of arguments for which a constexpr function can be used in a constant expression, the program is ill-formed. No diagnostic is required for this sort of ill-formed program because checking this condition from the function definition alone is intractable in general. Nevertheless, it's invalid C++, even if the compiler raises no error. But for some cases, it can be checked, and so a compiler could be obliged raise a diagnostic.
Your f without a condition falls into this category of ill-formed constructs. No matter how f is called, its execution will result in invoking emplace, which cannot appear in a constant expression. But it's easy enough to detect, so your compiler tells you it's a problem.
Your second version, with the condition, no longer invokes emplace unconditionally. Now its conditional. The condition itself is relying on a constexpr function, so it's not immediately ill-formed. Everything would depend on the arguments to the function (this included). So it doesn't raise an error immediately.
For example:
void foo()
{
if constexpr (...)
int x = 5;
else
double x = 10.0;
bar(x); // calls different overloads of bar with different values
}
It's common case in D lang, but I didn't found info about C++17.
Of course, it is possible to use something like
std::conditional<..., int, double>::type x;
but only in elementary cases. Even different initializators (as above) creates big problem.
There is no way this code could work. The problem is that x is out of scope when you are calling bar. But there is a workaround:
constexpr auto t = []() -> auto {
if constexpr(/* condition */) return 1;
else return 2.9;
}();
bar(t);
To explain a little bit, it uses instantly invoked lambda expression along with auto return type deduction. Therefore we are giving t value in place and it does not get out of the scope.
Of course, it wouldn't work if the if statement couldn't have been evaluated at compile time. And if you want to do some runtime operations inside of this lambda you cannot have t as constexpr, but it will still work.
There are two ways this won't work.
First, a variable is limited to the scope in which it's declared. Leaving out the braces isn't gonna fool the compiler: int x = 5 is still in its own scope, and disappears immediately after it appears.
Second, the relaxed grammar rules for if constexpr only apply within the if constexpr's body. It would be infeasible to allow the context created in the body to leak out to the surrounding scope, since by definition it may not be well-formed, or consistent between the then/else block. (What if the else-block declared x as a typename?)
Bottom line, you'll need to move bar() into the if-body, or template foo() itself and have the type and value of x determined by your ....
In C++ it's possible to declare variable inside parentheses like int (x) = 0;. But it seems that if you use this instead of variable name, then constructor is used instead: A (this); calls A::A(B*). So the first question is why it's different for this, is it because variables can't be named this? And to complicate matters a bit lets put this inside a lambda -
struct B;
struct A
{
A (B *) {}
};
struct B
{
B ()
{
[this] { A (this); } ();
}
};
Now gcc calls A::A(B*), msvc prints error about missing default constructor and clang prints expected expression (https://godbolt.org/g/Vxe0fF). It's even funnier in msvc - it really creates variable with name this that you can use, so it's definitely a bug (https://godbolt.org/g/iQaaPH). Which compiler is right and what are the reasons for such behavior?
In the C++ standard §5.1.5 (article 7 for C++11, article 8 later standard) [expr.prim.lambda]:
The lambda-expression’s compound-statement yields the function-body (8.4) of the function call operator, but
for purposes of name lookup (3.4), determining the type and value of this (9.2.2.1) and transforming id-
expressions referring to non-static class members into class member access expressions using (*this) (9.2.2),
the compound-statement is considered in the context of the lambda-expression. [ Example:
struct S1 {
int x, y;
int operator()(int);
void f() {
[=]()->int {
return operator()(this->x + y); // equivalent to S1::operator()(this->x + (*this).y)
// this has type S1*
};
}
};
— end example ]
Thus, gcc is right. You will notice that their is no exception about the fact that you are capturing this. Their is, however a precision since C++14 in the case where you capture *this, still in §5.1.5 (article 17):
If *this is captured by copy, each odr-use of this is transformed into a pointer to the corresponding
unnamed data member of the closure type, cast (5.4) to the type of this.
Please consider the following two C++14 programs:
Program 1:
struct S { constexpr int f() const { return 42; } };
S s;
int main() { constexpr int x = s.f(); return x; }
Program 2:
struct S { constexpr int f() const { return 42; } };
int g(S s) { constexpr int x = s.f(); return x; }
int main() { S s; return g(s); }
Are neither, either or both of these programs ill-formed?
Why/why not?
Both programs are well-formed. The C++14 standard requires that s.f() be a constant expression because it is being used to initialize a constexpr variable, and in fact it is a core constant expression because there's no reason for it not to be. The reasons that an expression might not be a core constant expression are listed in section 5.19 p2. In particular, it states that the evaluation of the expression would have to do one of several things, none of which are done in your examples.
This may be surprising since, in some contexts, passing a non-constant expression to a constexpr function can cause the result to be a non-constant expression even if the argument isn't used. For example:
constexpr int f(int) { return 42; }
int main()
{
int x = 5;
constexpr auto y = f(x); // ill-formed
}
However, the reason this is ill-formed is because of the lvalue-to-rvalue conversion of a non-constant expression, which is one of the things that the evaluation of the expression is not allowed to do. An lvalue-to-rvalue conversion doesn't occur in the case of calling s.f().
I can't seem to find a compelling passage or example in the standard that directly addresses the issue of calling a constexpr member function on a non-constexpr instance, but here are some that may be of help (from draft N4140):
[C++14: 7.1.5/5]:
For a non-template, non-defaulted constexpr function or a non-template, non-defaulted, non-inheriting
constexpr constructor, if no argument values exist such that an invocation of the function or constructor
could be an evaluated subexpression of a core constant expression (5.19), the program is ill-formed; no
diagnostic required.
constexpr int f(bool b)
{ return b ? throw 0 : 0; } // OK
constexpr int f() { return f(true); } // ill-formed, no diagnostic required
From this I take that the program is not outright ill-formed just because a constexpr function has a possible non-constexpr path.
[C++14: 5.19]:
int x; // not constant
struct A {
constexpr A(bool b) : m(b?42:x) { }
int m;
};
constexpr int v = A(true).m; // OK: constructor call initializes
// m with the value 42
constexpr int w = A(false).m; // error: initializer for m is
// x, which is non-constant
This is somewhat closer to your example programs, here a constexpr constructor may reference a non-constexpr variable depending on the value of the argument, but there is no error if this path is not actually taken.
So I don't think either program you presented should be ill-formed, but I cannot offer convincing proof :)
This sounds like a quiz question, and not presented by a student, but the professor testing the public on stackoverflow, but let's see...
Let's start with the One Definition Rule. It's clear neither version violates that, so they both pass that part.
Then, to syntax. Neither have syntax failures, they'll both compile without issue if you don't mind the potential blend of a syntax and semantic issue.
First, the simpler semantic issue. This isn't a syntax problem, but f(), in both versions, is the member of a struct, and the function clearly makes no change to the owning struct, it's returning a constant. Although the function is declared constexpr, it is not declared as const, which means if there were some reason to call this as a runtime function, it would generate an error if that attempt were made on a const S. That affects both versions.
Now, the potentially ambiguous return g(S()); Clearly the outer g is a function call, but S may not be so clear as it would be if written return g(S{}); With {} initializing S, there would be no ambiguity in the future should struct S be expanded with an operator() (the struct nearly resembles a functor already). The constructor invoked is automatically generated now, and there is no operator() to create confusion for the compiler at this version, but modern C++14 is supposed to offer clearer alternatives to avoid the "Most Vexing Parse", which g(S()) resembles.
So, I'd have to say that based on semantic rules, they both fail (not so badly though).