5

This question Access to constexpr variable inside lambda expression without capturing answered why the ref-capture in the below example isn't strictly neccessary. But on the other hand one gets an error, if it is captured.

The error seems to be triggered by the recursive nature of foo().

template<typename T>
constexpr int bar(const T& x) { // NOK
//constexpr int bar(T x) { // OK
    return x;
}

template<typename T>
int foo(const T& l) {
    constexpr auto x = l() - 1;
    auto y = [&]{return bar(x);}; // if ref-capture is used, the above bar(const T&) is NOK, why? 

    if constexpr(x <= 0) {
        return 42;
    }
    else {
        return foo(y);
    }
}

auto l2 = []{
    return 3;
};

int main() {
    foo(l2);
}
wimalopaan
  • 4,838
  • 1
  • 21
  • 39
  • It actually happens if you put anything in the capture-group (`[x]`, `[&x]`, `[&]`, ...), and it is related to `y` not being `constexpr` anymore at some point, so `l() - 1` is not a compile-time expression and the compilation fails. – Holt May 09 '18 at 07:45
  • With clang, this gives an error either way, which makes more sense to me. I don't see why GCC considers `l() - 1` a constant expression in this context. –  May 09 '18 at 07:47
  • 1
    That sounds strange, since `l() - 1` should be really `constexpr`. – wimalopaan May 09 '18 at 07:47
  • I think clang++ is wrong here. You can test, that `l() - 1` gives a constexpr with the following line in `main()`: `constexpr auto z = l2() - 1;` – wimalopaan May 09 '18 at 08:15
  • 1
    @wimalopaan When `foo` is instantiated, the parameter `l` is a reference that has not been initialised with a constant expression. References that have not been initialised with a constant expression cannot be used in constant expressions. –  May 09 '18 at 08:27

1 Answers1

6

If we use clang as a compiler, which is usually more relevant than gcc when it comes to language lawyer, we find out that a simplified example is very telling:

template<typename T>
int foo(T/*&*/ l) {
    constexpr auto x = l() - 1;

    if constexpr(x <= 0) {
        return 42;
    }
    else {
        return 0;
    }
}

auto l2 = []{
    return 3;
};

int main() {
    foo(l2);
}

Adding and removing reference in foo() signature makes program compiled or non-compiled. I believe, this has to do with bullet 12 on constant expression topic on cppreference :

an id-expression referring to a variable or a data member of reference type, unless it was initialized with a constant expression or its lifetime began within the evaluation of this expression

https://en.cppreference.com/w/cpp/language/constant_expression

So both of those statements seem to be not satisfied, as reference was not initialized with constant expression, and it's lifetime didn't begin with evaluation of expression.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
  • You can boil it down to the following: `constexpr auto z = 1; auto y = [&]{return bar(z);}; constexpr auto z2 = y();` With the ref-capture this does not compile. And this is the reason, as @SergeyA stated: `z` is ref-initialized from outside the lambda. – wimalopaan May 09 '18 at 08:48