The following code is accepted by GCC 7.2 and clang 5.0.0, but is rejected by Microsoft VS 2017 15.5.0 Preview 5 and Intel C++ compiler 19:
struct S { };
constexpr int f(S)
{
return 0;
}
int main()
{
auto lambda = [](auto x)
{
constexpr int e = f(x);
};
lambda(S{});
}
Microsoft:
<source>(12): error C2131: expression did not evaluate to a constant
Intel:
<source>(12): error: expression must have a constant value
constexpr int e = f(x);
^
<source>(12): note: the value of parameter "x" (declared at line 10) cannot be used as a constant
constexpr int e = f(x);
^
If I replace f(x) with f(decltype(x){}), both Microsoft and Intel do not complain. I understand that x is not a constant expression, but it is not used inside f. This is probably why GCC and clang do not complain.
I guess that Microsoft and Intel compilers are correct in rejecting this code. What do you think?
From [expr.const]:
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 lvalue-to-rvalue conversion unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const object with a preceding initialization, initialized with a constant expression, or
a non-volatile glvalue that refers to a subobject of a string literal, or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to a non-mutable subobject of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
[...]
In f(x), we do an lvalue-to-rvalue conversion on x. x isn't of integral or enumeration type, it's not a subobject of a string-literal, it's not an object defined with constexpr, and its lifetime did not begin with the evaluation of f(x).
That seems to make this not a core constant expression.
However, as Casey points out, since S is empty, nothing in its implicitly-generated copy constructor would actually trigger this lvalue-to-rvalue conversion. That would mean that nothing in this expression actually violates any of the core constant expression restrictions, and hence gcc and clang are correct in accepting it. This interpretation seems correct to me. constexpr is fun.
This is not a gcc/clang bug. The same behavior can be reproduced in C++11 with a template function:
template <typename T>
void foo(T x)
{
constexpr int e = f(x);
}
int main()
{
foo(S{});
}
on godbolt.org
The question is, given...
template <typename T>
void foo(T x)
{
constexpr int e = f(x);
}
...is f(x) a constant expression?
From [expr.const]:
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:
invocation of a function other than a constexpr constructor for a literal class, a constexpr function, or an implicit invocation of a trivial destructor
S{} and 0 are constant expressions because it doesn't violate any of the rules in [expr.const]. f(x) is a constant expression because it's an invocation to a constexpr function.
Unless I am missing something, gcc and clang are correct here.
Related
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.
Consider the code below:
template<char>
struct S { };
template<int N>
constexpr auto f(const char (&ref) [N]) {
return S<ref[0]>{};
}
int main() {
constexpr auto v = f("foo");
(void)v;
}
It doesn't compile for ref[0] is not a constant expression.
Anyway, the code below compiles fine:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return ref[0];
}
int main() {
constexpr auto v = f("foo");
(void)v;
}
Should both of them compile or fail to do that for more or less the same reason?
From [expr.const] we have that:
A conditional-expression e is a core constant expression unless the evaluation of e [...] 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;
Anyway, in this case, it is initialized with a constant expression and the lifetime is the same of e, thus the rule doesn't apply.
What's wrong in my reasoning?
As a side question, I would ask if it's possible to use such an array or part of it as a template argument.
This:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return S<ref[0]>{};
}
is ill-formed because according to [temp.arg.nontype]:
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of
the type of the template-parameter.
and from [expr.const]:
A conditional-expression e is a core constant expression unless the evaluation of e, following the rules of the
abstract machine (1.9), would evaluate one of the following expressions: [...]
(2.7) — an lvalue-to-rvalue conversion (4.1) unless it is applied to
(2.7.1) — a non-volatile glvalue of integral or enumeration type that refers to a complete non-volatile const
object with a preceding initialization, initialized with a constant expression, or
(2.7.2) — a non-volatile glvalue that refers to a subobject of a string literal (2.13.5), or
(2.7.3) — a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers to
a non-mutable sub-object of such an object, or
(2.7.4) — a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within
the evaluation of e;
ref[0] requires an lvalue-to-rvalue conversion and none of those sub-bullets apply. Note that ref is not a string literal, so 2.7.2 doesn't apply, nor is it defined with constexpr, because it's a function argument and we don't have that capability.
We basically need the ability to pass string literals as literals, which doesn't exist yet.
The other example:
template<int N>
constexpr auto f(const char (&ref) [N]) {
return ref[0];
}
doesn't have the converted constant expression required - that one was brought in by the template non-type argument. This code is fine, and would only be problematic if you tried to initialize a constexpr variable with a non-constexpr array value.
The first example shouldn't compile because you cannot have compiletime-only constexpr functions (or overload on compiletime-ness, like D's __cfte).
Following this reasoning, if you called first example's f at runtime, what would its return type be?
As to the side question: Boost Hana, despite supporting only the newest standard, only uses string literals for runtime stuff, so it may not be possible.
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.
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 the variable v in the sample code below odr-used?
extern void* v;
template<void*&>
void f() {}
int main()
{
f<v>();
}
I found this pattern in Boost ML.
cf. http://lists.boost.org/Archives/boost/2011/04/180082.php
It says that the boost::enabler is never defined, but clang rejects it as a linkage error if -g option is provided.
cf. http://melpon.org/wandbox/permlink/nF45k7un3rFb175z
The sample code above is reduced version of the Boost ML's code and clang rejects it too.
cf. http://melpon.org/wandbox/permlink/ZwxaygXgUhbi1Cbr
I think (but I am not sure) that template non-type arguments for reference type are odr-used even if they are not referred in their template body so the Boost ML's pattern is ill-formed.
Is my understanding correct?
I believe v is odr-used. f<v> is a template-id (14.2) whose template-argument is an id-expression (5.1.1) - a form of expression. It's clearly not an unevaluated operand (it doesn't appear as an operand of typeid, sizeof, noexcept or decltype), so it's potentially evaluated per 3.2/2:
3.2/2 An expression is potentially evaluated unless it is an unevaluated operand (Clause 5) or a subexpression thereof...
At which point, we have
3.2/3 A variable x whose name appears as a potentially-evaluated expression ex is odr-used unless [a condition that doesn't appear to apply here as no lvalue-to-rvalue conversion is applied].
[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) [..]
Unfortunately, applying the l-t-r conversion to v at this point would not yield a constant expression - [expr.const]/2:
A conditional-expression e is a core constant expression unless
the evaluation of e, following the rules of the abstract machine
(1.9), would evaluate one of the following expressions: [..]
— an
lvalue-to-rvalue conversion (4.1) unless it is applied to
a non-volatile glvalue of integral or enumeration type that refers to a non-volatile const object with a preceding
initialization, initialized with a constant expression [..], or
a non-volatile glvalue that refers to a non-volatile object defined with constexpr, or that refers
to a non-mutable sub-object of such an object, or
a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;
However, though the implementation suggested by Matt isn't correct, the idea certainly is. A simple way of using this approach is demonstrated in this answer, using a helper template. In your case, try
template <bool cond, int id=0>
using distinct_enable_if =
typename std::enable_if<cond, std::integral_constant<int,id>*>::type;
class test
{
public:
template< class... T,
distinct_enable_if<sizeof...(T) == 10> = nullptr>
test( T&&... ) {}
template< class T,
distinct_enable_if<std::is_arithmetic<T>{}> = nullptr>
operator T() const { return T{}; }
/* Note the additional template argument:
It ensures that the template parameter lists are not identical,
and the ODR isn't violated */
template< class T,
distinct_enable_if<std::is_pointer<T>{}, 1> = nullptr>
operator T() const { return T{}; }
};
Demo.