llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
26.82k stars 10.99k forks source link

Clang allows invalid type conversion of lambda from `void` to `int` in default argument and template class #96753

Open Rush10233 opened 1 week ago

Rush10233 commented 1 week ago

The following code contains an invalid type conversion:

template <int> 
struct S
{
  friend void foo (int a = []{
    [&](){
        return 0; 
    }();
  }()) {}
};
S<0> t;

In line 4 , the default argument value of the integer variable a is set to a nested lambda expression with the inner one returning an integer and the outer one returning nothing. The code compiles well in clang, while GCC outputs the following error message:

<source>: In instantiation of 'struct S<0>':
<source>:10:6:   required from here
   10 | S<0> t;
      |      ^
<source>:8:2: error: could not convert '<lambda closure object>S<0>::<lambda()>().S<0>::<lambda()>()' from 'void' to 'int'
    4 |   friend void foo (int a = []{
      |                            ~~~
    5 | [&](){
      | ~~~~~~~~~~~~~~
    6 |  return 0;
      |  ~~~~~~~~~
    7 | }();
      | ~~~~
    8 | }()) {}
      | ~^~
      |  |
      |  void
<source>:4:15: note:   when instantiating default argument for call to 'void foo(int) [with int <anonymous> = 0]'
    4 |   friend void foo (int a = []{
      |               ^~~

Please note that if we replace the template class S with a non-template one, clang rejects the code by giving a similar error message with GCC.

https://godbolt.org/z/v1Gs3Kcf6

llvmbot commented 1 week ago

@llvm/issue-subscribers-clang-frontend

Author: None (Rush10233)

The following code contains an invalid type conversion: ```c++ template <int> struct S { friend void foo (int a = []{ [&](){ return 0; }(); }()) {} }; S<0> t; ``` In line 4 , the default argument value of the integer variable `a` is set to a nested lambda expression with the inner one returning an integer and the outer one returning nothing. The code compiles well in clang, while GCC outputs the following error message: ```c++ <source>: In instantiation of 'struct S<0>': <source>:10:6: required from here 10 | S<0> t; | ^ <source>:8:2: error: could not convert '<lambda closure object>S<0>::<lambda()>().S<0>::<lambda()>()' from 'void' to 'int' 4 | friend void foo (int a = []{ | ~~~ 5 | [&](){ | ~~~~~~~~~~~~~~ 6 | return 0; | ~~~~~~~~~ 7 | }(); | ~~~~ 8 | }()) {} | ~^~ | | | void <source>:4:15: note: when instantiating default argument for call to 'void foo(int) [with int <anonymous> = 0]' 4 | friend void foo (int a = []{ | ^~~ ``` Please note that if we replace the template class `S` with a non-template one, clang rejects the code by giving a similar error message with GCC. [https://godbolt.org/z/v1Gs3Kcf6](https://godbolt.org/z/v1Gs3Kcf6)
zygoloid commented 1 week ago

Clang does reject if you try to instantiate the default argument: https://godbolt.org/z/nno7G7v65 GCC instead rejects if you instantiate the class. Testcase reduces to:

template <typename T> struct S {
  friend void foo (S, int a = T::error) {}
};

// GCC rejects this instantiation, Clang accepts
S<int> t;

// Clang rejects this instantiation
void f() { foo(t); }

So the question is, is this default argument expression supposed to be instantiated with the class or not?

As far as I can see, GCC is right here: delayed instantiation of default arguments applies only to member functions and function templates (see also here), and a friend function of a class template is neither, so its default arguments should be instantiated with the class.

Interestingly, the same applies to noexcept-specifiers, which GCC gets wrong like Clang: https://godbolt.org/z/vE3o8Pb6d