Skip to content

Various issues with using uncaptured constexpr variable in lambda #127086

@davidstone

Description

@davidstone

I cannot figure out what causes clang to decide that I odr-used a variable (thus requiring a capture), and failing to capture a variable clang has decided I odr-used causes error messages in unrelated sections of code.

First, clang rejects the following:

void f() {
	constexpr bool b = true;
	[] { b; };
}

with

<source>:3:7: error: variable 'b' cannot be implicitly captured in a lambda with no capture-default specified
    3 |         [] { b; };
      |              ^
<source>:2:17: note: 'b' declared here
    2 |         constexpr bool b = true;
      |                        ^
<source>:3:2: note: lambda expression begins here
    3 |         [] { b; };
      |         ^
<source>:3:3: note: capture 'b' by value
    3 |         [] { b; };
      |          ^
      |          b
<source>:3:3: note: capture 'b' by reference
    3 |         [] { b; };
      |          ^
      |          &b
<source>:3:3: note: default capture by value
    3 |         [] { b; };
      |          ^
      |          =
<source>:3:3: note: default capture by reference
    3 |         [] { b; };
      |          ^
      |          &
<source>:3:7: warning: expression result unused [-Wunused-value]
    3 |         [] { b; };
      |              ^
<source>:3:2: warning: expression result unused [-Wunused-value]
    3 |         [] { b; };
      |         ^~~~~~~~~
2 warnings and 1 error generated.
Compiler returned: 1

https://godbolt.org/z/Mva5ffYoe

but accepts

void f() {
	constexpr bool b = true;
	[] { +b; };
}

It also rejects

void f() {
	constexpr bool b = true;
	[] { static_cast<void>(b); };
}

but accepts

void f() {
	constexpr bool b = true;
	[] { static_cast<bool>(b); };
}

It accepts

template<typename T>
void f() {
	constexpr bool b = T::b;
	[] { b; };
}

until we try to actually instantiate the function:

template<typename T>
void f() {
	constexpr bool b = T::b;
	[] { b; };
}

struct s {
	static constexpr bool b = true;
};

void g() {
	f<s>();
}

even though whether b is odr-used is not a dependent property.

When b is used as part of an expression that instantiates a template inside of a template, the error message is wrong (and has gotten more wrong with clang trunk). First, when using a regular function, clang trunk and clang 19.1.0 agree on the wrong error message:

template<int>
void templ() {
}


template<typename>
void f() {
	constexpr bool b = true;
	[] {
		b, templ<0>();
	};
}

causes clang to report

<source>:10:6: error: no matching function for call to 'templ'
   10 |                 b, templ<0>();
      |                    ^~~~~~~~
<source>:2:6: note: candidate template ignored: invalid explicitly-specified argument for 1st template parameter
    2 | void templ() {
      |      ^
<source>:10:3: warning: left operand of comma operator has no effect [-Wunused-value]
   10 |                 b, templ<0>();
      |                 ^
1 warning and 1 error generated.
Compiler returned: 1

https://godbolt.org/z/M4vE39K5q

If the template is a user-defined literal, clang trunk and clang 19.1.0 give different error messages:

template<char...>
int operator""_literal() {
	return 0;
}

template<typename>
void f() {
	constexpr bool b = true;
	[] {
		b, 0_literal;
	};
}

trunk:

<source>:10:7: error: no matching function for call to 'operator""_literal'
   10 |                 b, 0_literal;
      |                     ^
<source>:2:5: note: candidate template ignored: invalid explicitly-specified argument for 1st template parameter
    2 | int operator""_literal() {
      |     ^
1 error generated.
Compiler returned: 1

19.1.0:

<source>:10:3: error: variable 'b' cannot be implicitly captured in a lambda with no capture-default specified
   10 |                 b, 0_literal;
      |                 ^
<source>:8:17: note: 'b' declared here
    8 |         constexpr bool b = true;
      |                        ^
<source>:9:2: note: lambda expression begins here
    9 |         [] {
      |         ^
<source>:9:3: note: capture 'b' by value
    9 |         [] {
      |          ^
      |          b
<source>:9:3: note: capture 'b' by reference
    9 |         [] {
      |          ^
      |          &b
<source>:9:3: note: default capture by value
    9 |         [] {
      |          ^
      |          =
<source>:9:3: note: default capture by reference
    9 |         [] {
      |          ^
      |          &
<source>:10:3: warning: left operand of comma operator has no effect [-Wunused-value]
   10 |                 b, 0_literal;
      |                 ^
1 warning and 1 error generated.
Compiler returned: 1

https://godbolt.org/z/eTfx6evnz

And if the template is a user-defined literal and constexpr variable's value is dependent on a template parameter then clang 19.1.0 accepts the code and clang trunk rejects (until you instantiate the template):

template<char...>
int operator""_literal() {
	return 0;
}

template<typename T>
void f() {
	constexpr bool b = T::b;
	[] {
		b, 0_literal;
	};
}
<source>:10:7: error: no matching function for call to 'operator""_literal'
   10 |                 b, 0_literal;
      |                     ^
<source>:2:5: note: candidate template ignored: invalid explicitly-specified argument for 1st template parameter
    2 | int operator""_literal() {
      |     ^
1 error generated.
Compiler returned: 1

https://godbolt.org/z/cdo3jzWdq

And if you make the expression you use dependent in other ways, then clang 19.1.0 actually accepts the code even on instantiation:

template<char...>
int operator""_literal() {
	return 0;
}

template<typename T>
void f() {
	constexpr bool b = T::b;
	[] {
		b ? 0_literal : 0;
	};
}

struct s {
	static constexpr bool b = true;
};

void g() {
	f<s>();
}

clang trunk's error:

<source>:10:8: error: no matching function for call to 'operator""_literal'
   10 |                 b ? 0_literal : 0;
      |                      ^
<source>:2:5: note: candidate template ignored: invalid explicitly-specified argument for 1st template parameter
    2 | int operator""_literal() {
      |     ^
<source>:9:2: warning: expression result unused [-Wunused-value]
    9 |         [] {
      |         ^~~~
   10 |                 b ? 0_literal : 0;
      |                 ~~~~~~~~~~~~~~~~~~
   11 |         };
      |         ~
<source>:19:2: note: in instantiation of function template specialization 'f<s>' requested here
   19 |         f<s>();
      |         ^
1 warning and 1 error generated.
Compiler returned: 1

https://godbolt.org/z/qd5Mf3oP3

This is actually the version of the bug I ran into in the wild. I had apparently working code rejected by a recent build of clang because I had code like the last pattern I showed: I set a constexpr local variable to the result of a function dependent on a template parameter, then used its value as the first argument of the conditional operator that used a user-defined literal in one of the other arguments.

Note that gcc accepts all of my examples, and I believe it is correct to do so. Lambda bodies that do odr-use the constexpr variable (such as &b) are correctly rejected by all compilers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    clang:frontendLanguage frontend issues, e.g. anything involving "Sema"confirmedVerified by a second partyconstexprAnything related to constant evaluationlambdaC++11 lambda expressions

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions