Is it OK to use lambda function parameter as a constant expression? - c++

Why in this example the first call doesn't compile and the second one compiles?
consteval auto foo(auto x) {
static_assert(x);
}
int main(){
foo(42); // error: non-constant condition for static assertion
foo([]{}); // OK
}
If I understand correctly, the first one is wrong due to lvalue-to-rvalue conversion not being a constant expression. Why then the second one is OK?

static_assert(x); while passing []{} works, because a capture-less lambda has a conversion operator to function pointer and a function pointer can be converted to a bool (which is going to be true for everything but a null pointer which the conversion can't return).
An expression is a core constant expression as long as it doesn't fall in any of a number of exceptions listed in [expr.const]/5. In relation to the potentially relevant exceptions, neither is x a reference, which would disqualify the expression x from being a constant expression immediately, nor is a lvalue-to-rvalue conversion on x or one of its subobjects required in the call to the conversion function or the conversion to bool. The returned function pointer is in no way dependent on the value of the lambda. The call to the conversion function is also allowed in a constant expression, since it is specified to be constexpr ([expr.prim.lambda.closure]/11), so that the exception of calling a non-constexpr function also doesn't apply.
None of the exceptions apply and x (including the conversions to bool) is a constant expression. The same is not true if 42 is passed, because the conversion to bool includes an lvalue-to-rvalue conversion on x itself.

Related

Stability and uniqueness of lambda-to-function-pointer conversion

A capture-less lambda can be converted to a function pointer with the same parameter list as the lambda expression.
I am wondering whether this conversion is guaranteed to be stable, i.e. given a capture-less lambda expression, is it guaranteed by the standard that the function pointer conversion of any object of its type will always yield the same pointer value?
Furthermore, is it guaranteed that this pointer value is unique among lambda expressions and other functions?
auto x = []{};
auto x2 = x;
auto y = []{};
assert(+x == +x2); // ?
assert(+x != +y); // ?
The wording would seem to suggest that there's one function per type, so assert(+x == +x2); at least should hold:
[expr.prim.lambda.closure]/7 The closure type for a non-generic lambda-expression with no lambda-capture ... has a conversion function to pointer to function... The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type.
This appears to say that there's a single function F, and that all instances of the closure type should convert to that function.
The standard doesn't seem to require or prohibit that distinct closure types convert to distinct functions. On the surface, it appears that +x != +y could go either way.

Process of conversion of types inside selection and iteration statements in C++

According To C++ ISO:
The value of a condition that is an initialized declaration in a
statement other than a switch statement is the value of the declared
variable contextually converted to bool (7.3). If that conversion is
ill-formed, the program is ill-formed. The value of a condition that
is an initialized declaration in a switch statement is the value of
the declared variable if it has integral or enumeration type, or of
that variable implicitly converted to integral or enumeration type
otherwise. The value of a condition that is an expression is the value of the
expression, contextually converted to bool for statements other than switch; if that conversion is ill-formed,
the program is ill-formed.
The following quote comes from the section 7.3 said above:
Certain language constructs require that an expression be converted to
a Boolean value. An expression e appearing in such a context is said
to be contextually converted to bool and is well-formed if and only if
the declaration bool t(e); is well-formed, for some invented temporary
variable t (9.4).
Based on these two, I got an idea that switch-statement not always conducts conversions if it has the appropriate type. Otherwise if-statement looks to always perform such conversion, even if I do something like if (true){},I understood the true value would be converted. So, is it what happens? The code: if(true){} will convert true to boolean?(even true already being a boolean)
Comments already discussed that you are misinterpreting the two paragraphs. I will focus on the second. The important message is the following:
There are certain contexts where values might implicitly undergo converions even though the conversion is actually only possible explicitly.
Some code examples might help:
struct foo{
explicit operator bool() {return true;}
};
int main()
{
foo f;
bool b(f); // fine !
bool c = f; // error! no viable conversion from f to bool
// because foo::operator bool is explicit
}
A foo can be converted to a bool but the conversion operator is explicit. Hence bool b(f) (explicit conversion) is fine, while bool c = f; (implicit conversion) is not.
Now, there are certain contexts where...
An expression e appearing in such a context is said to be contextually converted to bool and is well-formed if and only if the declaration bool t(e); is well-formed, for some invented temporary variable t (9.4).
This explains some special case of conversions. Such conversion happen implicitly, but are well-formed exactly when the explicit conversion would be well formed.
With out such "contextual conversion", this would be a compiler error:
foo f;
if (f) {}
However, because f is contextually convertible to bool, the code is ok. Without this special rule for contextual conversion one would have to write if (bool(f)) because foo::operator bool is explicit.
In other words, the paragraph is not about bools getting converted to bool, but rather it explains an exception from the usual implicit / explicit conversions, that are applied when necessary.

Why references can't be used with compile time functions?

I have two snippets.
The first snippet:
#include <string>
template <typename T>
constexpr bool foo(T&&) {
return false;
}
int main() {
std::string a;
if constexpr (foo(a)) {
}
}
The second snippet:
#include <string>
template <typename T>
constexpr bool foo(T&&) {
return false;
}
int main() {
std::string a;
std::string& x = a;
if constexpr (foo(x)) {
}
}
The first one compiles, but the second one does not compile (error message: error: the value of ‘x’ is not usable in a constant expression. Why? Why a is usable in a constant expression and x is not?
The command, used to compile g++ -std=c++17 main.cpp.
Because usually a constant expression cannot evaluate a reference that refers to an object with automatic storage duration. Here I mean "evaluate" by determining the identity of the object, not by determining the value of the object. So even the value of the object a is not required in your example (i.e. no lvalue-to-rvalue conversion is applied), foo(x) is still not a constant expression.
Note foo(a) does not evaluate any reference. Although the parameter of foo is a reference, it is not evaluated as an expression. In fact, even if it was evaluated, for example,
template <typename T>
constexpr bool foo(T&& t) {
t;
return false;
}
foo(a) is still a constant expression. Such cases are exceptions as the reference t is initialized within the evaluation of foo(a).
Related part in the standard (irrelevant part is elided by me):
[expr.const]/2:
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 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;
...
[expr.const]/6:
A constant expression is either a glvalue core constant expression that refers to an entity that is a permitted result of a constant expression (as defined below), ...
An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.
Because you can't know at compile time what value x will have, all you know is that it will point to a. What you are doing is checking the "value" of x, but the value of x is the address where a is and you can't know where a will be allocated nor an address is constant.
On the other hand you already know the value of a, it's an empty std::string.
This question contains some more details: how to initialize a constexpr reference
Firstly, In your both code sections, the evaluation context all require the postfix-expression to be a constant expression. The definition of function template foo satisfies the requirements of a constexpr function due to its parameter is of literal type.
id-expression a and x are all glvalue which evaluation determine the identity of an object, but the only difference is that, In your fist code. copy-initialize the parameter from a only require identity conversion, because of the following rules:
When a parameter of reference type binds directly to an argument expression, the implicit conversion sequence is the identity conversion, unless the argument expression has a type that is a derived class of the parameter type, in which case the implicit conversion sequence is a derived-to-base Conversion
And it does nothing, due to
[over.ics.scs#2]
As described in Clause [conv], a standard conversion sequence is either the Identity conversion by itself (that is, no conversion)
And the remain expressions are all constant expressions. So, for foo(a) this expression, it's a constant expression.
For foo(x), because x is an id-expression of reference type that subject to this rule:
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;
The lifetime of x began before the evaluation of foo(x) and it isn't initialized with a constant expression because std::string a; is not a glvalue constant expression. If you modify the std::string a; to static std::string a;,then it will be ok.

Converting integral type to enum: functional cast vs initialization

Assuming there is an enum like this:
enum foo: int {
first,
second
}
Then I use it as follows:
foo f(1); // error: cannot initialize a variable of type 'foo' with an rvalue of type 'int'
foo f = foo(1); // OK !
I was wondering what is the difference between the two ?
I understand that the second version can be seen as a functional-style cast but why does this make any difference ?
For example, if I do this:
class Bar {};
Bar b = Bar(1); // no matching conversion for functional-style cast from 'int' to 'Bar'
I obviously get an error which makes sense. Therefore, this leads me to believe that in order for the second version of the foo example above to work there must be a conversion from int to enum defined somewhere but if there is such a conversion then why do I get an error in the first version ?
I do apologize if this is a duplicate. I am suspecting it is.
This seems relevant: Is this a cast or a construction?
... but not quit.
Thanks in advance !
Yes, the two forms are quite different, in a subtle way. Let's look at the first one, which results in an error. It's initialization of f, of type foo, from an int. It's described here, emphasis mine:
[dcl.init]/17.8
Otherwise, the initial value of the object being initialized is the
(possibly converted) value of the initializer expression. Standard
conversions will be used, if necessary, to convert the initializer
expression to the cv-unqualified version of the destination type; no
user-defined conversions are considered. If the conversion cannot be
done, the initialization is ill-formed.
The pertinent conversions in this case are integral conversions, mainly the one specified by the following:
[conv.integral]/1
A prvalue of an unscoped enumeration type can be converted to a
prvalue of an integer type.
So a an unscoped enumeration can be converted to an integer implicitly, but the converse is not true. Which is why the initialization is ill-formed. However, that functional-style cast notation is essentially a static cast. And a static cast can perform the inverse of (almost) any valid standard conversion. So the casted 1 is then used to initialize f, but at this point we are copy-initializing from a foo prvalue, which is of course perfectly fine.

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