llvm / llvm-project

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

[clang] Failure when upcasting to a base class in trailing return type expression #114024

Open carlosgalvezp opened 3 days ago

carlosgalvezp commented 3 days ago

Hi,

Consider this example code:


struct Base
{};

int foo(Base&);

struct Derived : Base
{
    auto f() & -> decltype(foo(static_cast<Base&>(*this)))
    {
        return foo(static_cast<Base&>(*this));
    }
};

Clang emits this failure:

<source>:9:32: error: non-const lvalue reference to type 'Base' cannot bind to a value of unrelated type 'Derived'
    9 |     auto f() & -> decltype(foo(static_cast<Base&>(*this)))
      |  

Repro.

However, GCC, MSVC and EDG accept the code. I am confused as to why Clang complains; a Derived class should be possible to upcast to its base?

Thanks!

llvmbot commented 3 days ago

@llvm/issue-subscribers-clang-frontend

Author: Carlos Galvez (carlosgalvezp)

Hi, Consider this example code: ```cpp struct Base {}; int foo(Base&); struct Derived : Base { auto f() & -> decltype(foo(static_cast<Base&>(*this))) { return foo(static_cast<Base&>(*this)); } }; ``` Clang emits this failure: ```console <source>:9:32: error: non-const lvalue reference to type 'Base' cannot bind to a value of unrelated type 'Derived' 9 | auto f() & -> decltype(foo(static_cast<Base&>(*this))) | ``` [Repro](https://godbolt.org/z/nns6EKhzM). However, GCC accepts the code. I am confused as to why Clang complains; a Derived class should be possible to upcast to its base? Thanks!
Rajveer100 commented 3 days ago

@carlosgalvezp This way works:

auto f() & -> auto
carlosgalvezp commented 3 days ago

@Rajveer100

decltype(auto) is unfortunately not SFINAE-friendly, so I don't believe I can use that.

keinflue commented 2 days ago

The return type is a not a complete-class context. Derived is not complete yet at this point and static_cast ignores inheritance-based casts if the derived class type is incomplete, even if the bases have been seen already.

keinflue commented 2 days ago

You do not need to cast from this though. The type and value category of the static_cast expression doesn't depend on the argument and because you already know that the cast will be valid once the class is complete, there is no SFINAE implication specific to the cast either:

auto f() & -> decltype(foo(std::declval<Base&>()))
carlosgalvezp commented 2 days ago

Thanks, that works! Still it would be good if we could keep an exact copy of the return expression into the return type, which is a common idiom. Some people even do this via a macro to ensure the 3 places where it's needed receive the same expression.

Isn't this still a Clang issue given that all other major compilers accept the code?

keinflue commented 2 days ago

From what I can tell the other compilers are not standard-conforming and Clang behaves according to the standard. So fixing this would probably require a proposal to change the standard.

(But I am just an interested user, so wait for some authoritative answer.)

carlosgalvezp commented 2 days ago

Ah, I see. Do you know in particular which section of the standard is relevant in this case?

keinflue commented 2 days ago

The implicit cast (which is also used by static_cast) is specified in [conv.ptr]/3. It explicitly requires the derived type to be complete.

A class is complete only after its closing } bracket (i.e. when it is "reachable", see definition in [module.reach])) per [class.mem.general]/8 except in complete-class contexts, which are listed in [class.mem.general]/7 and don't include the trailing return type.

carlosgalvezp commented 2 days ago

Thank you for the thorough explanation!