20

Consider this example which declares a variable as constexpr, captures it by copy in a lambda, and declares another constexpr variable which is the result of a constexpr function unwrapping a non-type template parameter from the original variable.

#include <utility>

template<int I>
constexpr auto unwrap(std::integral_constant<int, I>) {
  return I;
}

int main() {
  constexpr auto i = std::integral_constant<int, 42>{};
  constexpr auto l = [i]() {
    constexpr int x = unwrap(i);
  };
}

Clang (trunk) accepts this code. (wandbox)

GCC (trunk) fails with the following error message (wandbox):

lambda_capture.cpp:11:31: error: the value of ‘i’ is not usable in a constant expression
     constexpr int x = unwrap(i);
                               ^
lambda_capture.cpp:10:28: note: ‘i’ was not declared ‘constexpr’
   constexpr auto l = [i]() {

Which compiler is correct? It seems to me that this is a GCC bug, where the constexpr-ness of lambda captures is not correctly propagated to the lambda context.

Jackie
  • 796
  • 5
  • 14
  • 8
    Considering that C++17 is not a thing yet I wouldn't say that any of them is right or wrong. – freakish Jun 06 '17 at 09:38
  • 2
    Agree with @freakish, compilers might be reasonably hesitant to implement anything that isn't set in concrete yet. – Passer By Jun 06 '17 at 09:40
  • 7
    The C++17 standard draft is in the [final stages of standardization](https://isocpp.org/std/status) and GCC and Clang both claim to be [language-feature complete](http://en.cppreference.com/w/cpp/compiler_support). Although it's early, I think it's reasonable to ask questions about how compilers implement the new standard, and it's helpful for the compiler developers to discuss how things should eventually work. – Jackie Jun 06 '17 at 09:45
  • I know [it](https://stackoverflow.com/questions/13468989/lambda-capturing-constexpr-object) is not about c++17 but may be helpful – W.F. Jun 06 '17 at 10:24

1 Answers1

7

Both implementations are bugged, but I'm inclined to think that GCC got the right answer here.


Dropping the capture of i causes Clang to refuse to compile the code. That means it clearly has a bug somewhere.

[expr.const]/2.12:

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:

  • [...]
  • in a lambda-expression, a reference to [...] a variable with automatic storage duration defined outside that lambda-expression, where the reference would be an odr-use;
  • [...]

Clang's behavior is schizophrenic: if the use of i in the body is not an odr-use, then it doesn't need to be captured, yet it rejects the code in the OP if the explicit capture is removed; OTOH, if it is an odr-use, then by the above unwrap(i) isn't a constant expression, and so it should reject the initialization of x.


GCC's lambda implementation is woefully bad with respect to odr-use. It does constant-folding ultra-early, resulting in all kinds of subtle mischief. On the other hand, for explicit captures it transforms all uses, whether or not it's actually an odr-use. The aggressive constant folding means that it accepts OP's code if the capture of i is removed.

Assuming that unwrap(i) does odr-use i, then it is correct that, per [expr.const]/2.12, OP's code is ill-formed.


Does unwrap(i) actually odr-use i? That question boils down to whether copy-initializing the parameter object of unwrap counts as applying an lvalue-to-rvalue conversion to i. I don't see anything in the standard that explicitly says that an lvalue-to-rvalue conversion is applied here, and instead [dcl.init]/17.6.2 indicates that we call a constructor (in this case, the trivial implicitly defined copy constructor) passing i as the argument bound to its parameter, and reference binding is a classic example of odr-use.

To be sure, applying an l-to-r conversion would result in a copy-initialization of an integral_constant<int, 42> object from i, but the problem here is that nothing in the standard says the converse - that all copy-initializations of an integral_constant<int, 42> object from i count as l-to-r conversions.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • So you're saying that even if the implicit copy constructor is constexpr, it still counts as an odr-use of `i` and the closure type is a core constant expression. This is frustrating, especially since it sounds like GCC's acceptable of `i` without a capture is also a defect, because I would like to be able to use constexpr variables defined outside of the lambda in a constexpr lambda. Do you think the wording in the standard is inconsistent, or do you think there's a reason it's written this way? – Jackie Jun 07 '17 at 08:42
  • It seems that removal of the capture reverses behaviour of both compilers. – n. m. could be an AI Jun 07 '17 at 18:16
  • Are there bug reports for these? – Shafik Yaghmour Dec 31 '18 at 07:38