Reputation: 288
Take the following code:
template <typename T, typename U>
constexpr bool can_represent(U&& w) noexcept
{
return [] (auto&& x) {
try {
return T(std::forward<U>(x)) == std::forward<U>(x);
} catch(...) {
return false;
}
} (std::forward<U>(w));
}
I am using this function in a constant expression (template).
gcc
compiles it without a problem. clang
and MSVC
don't, lamenting that the function does not result in a constant expression.
Indeed, gcc
did not immediately accept this either; it was getting hung up on the try
, that normally wouldn't be allowed in a constexpr
function. That's why I had to use an immediately invoked lambda expression. However, now it works, and considering it only works with gcc
I'm quite confused.
Which compiler is correct?
Is there a property of the lambda that permits this to work in a constexpr
context, or is this some kind of non-standard gcc
extension?
[I've used godbolt to compile with clang
and MSVC
, where as I have gcc 8.1.0
on my machine]
Upvotes: 1
Views: 1203
Reputation: 17072
[gcc] was getting hung up on the
try
, that normally wouldn't be allowed in aconstexpr
function.
This is correct for a C++17 program. (C++20 relaxed this, so a try
block can now be used in a constexpr
function. However, it is only the try
that is allowed; it is not allowed for execution to hit something that throws an exception.)
That's why I had to use an immediately invoked lambda expression.
The implication here is that your approach made your code valid. This is incorrect. Using an an immediately invoked lambda did not work around the problem; it swept the problem under the rug. The try
is still a problem, but now compilers do not have to tell you it is a problem.
Using a lambda switches the constexpr
criterion from the straight-forward "the function body must not contain a try-block" to the indirect "there exists at least one set of argument values such that an invocation of the function could be an evaluated subexpression of a core constant expression". The tricky part here is that a violation of the latter criterion is "no diagnostic required", meaning that all the compilers are correct, whether or not they complain about this code. Hence my characterization of this as sweeping the problem under the rug.
So why is... that criterion is a long thing to repeat... what's the issue involving "core constant expressions"? C++17 removed the prohibition against lambdas in core constant expressions, so that much looks good. However, there is still a requirement that all function calls within the constexpr
function also be themselves constexpr
. Lambdas can become constexpr
in two ways. First, they can be explicitly marked constexpr
(but if you do that here, the complaint about the try
block should come back). Second, they can simply satisfy the constexpr
function requirements. However, your lambda contains a try
, so it is not constexpr
(in C++17).
Your lambda is not a valid constexpr
function. Hence calling it is not allowed in a core constant expression. There is no execution path through can_represent()
that avoids invoking your lambda. Therefore, can_represent
is not a valid constexpr
function, no diagnostic required.
Upvotes: 3