llvm / llvm-project

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

[clang][C++] Confusing error message after triggering a hard error in a function template specialization #109359

Open frederick-vs-ja opened 1 week ago

frederick-vs-ja commented 1 week ago

Currently, when the same ill-formed function template specialization is instantiated more than once, Clang emits strange error messages. Godbolt link.

#include <new>

namespace fvs {   
template<class T, class...Args,
    class Ret = decltype(::new(static_cast<void*>(nullptr)) T(*static_cast<Args&&(*)()>(nullptr)()...))>
Ret construct_at(T* loc, Args&&... args)
{
    // intentionally no const_cast, see https://cplusplus.github.io/LWG/issue3870
    return ::new (static_cast<void*>(loc)) T(static_cast<Args&&>(args)...);
}

template<class T, class...Args,
    class Ret = decltype(::new(static_cast<void*>(nullptr)) T(*static_cast<Args&&(*)()>(nullptr)()...))>
Ret construct_at_v2(T* loc, Args&&... args)
{
    return fvs::construct_at(loc, static_cast<Args&&>(args)...);
}
}

int main()
{
    int n{};
    const int* p = &n;
    fvs::construct_at(p);
    fvs::construct_at_v2(p);
}

Error messages:

<source>:9:19: error: static_cast from 'const int *' to 'void *' is not allowed
    9 |     return ::new (static_cast<void*>(loc)) T(static_cast<Args&&>(args)...);
      |                   ^~~~~~~~~~~~~~~~~~~~~~~
<source>:25:10: note: in instantiation of function template specialization 'fvs::construct_at<const int, const int *>' requested here
   25 |     fvs::construct_at(p);
      |          ^
<source>:17:12: error: no matching function for call to 'construct_at'
   17 |     return fvs::construct_at(loc, static_cast<Args&&>(args)...);
      |            ^~~~~~~~~~~~~~~~~
<source>:26:10: note: in instantiation of function template specialization 'fvs::construct_at_v2<const int, const int *>' requested here
   26 |     fvs::construct_at_v2(p);
      |          ^
<source>:6:5: note: candidate template ignored: substitution failure [with T = const int, Args = <>, Ret = decltype(::new (static_cast<void *>(nullptr)) const int(/*implicit*/(const int)0))]
    6 | Ret construct_at(T* loc, Args&&... args)
      |     ^
2 errors generated.
Compiler returned: 1

It seems that Clang treats the ill-formedness after the first instantiation failure as something like substitution failure.

Clang started to behave like this since 3.4, while MSVC and GCC don't do so. It's unclear to me whether this is intended.

shafik commented 1 week ago

CC @erichkeane

erichkeane commented 1 week ago

I don't know the historical reason here, but I think I dislike this behavior as well. It is really unfortunate, and I would expect that because the declaration successfully instantiated, that we could just leave the body with a RecoveryExpr/ExprError in it in this case instead.

But I don't have a good hold as to why this has happened. It might be worth tracking down what patch caused this.

erichkeane commented 1 week ago

Actually... looking at Godbolt, I don't think there IS any change in behavior for this, it looks like Clang was just giving up after that first error on that before. So I am no longer sure that tracking down a patch would be beneficial.