llvm-project
llvm-project copied to clipboard
Clang incorrectly instantiates class during CTAD
Consider this C++17 code, which compiles fine with GCC but fails with Clang (at least Clang 9 to Clang 15). It is an MWE that I condensed from GCC's STL implementation of std::pair and demonstrates a problem of calling std::pair's constructor with a function:
#include <type_traits>
template <typename _T1>
struct _PCC
{
template <typename _U1>
static constexpr bool ConstructibleFrom()
{
return std::is_constructible<_T1, const _U1&>::value;
}
};
template<class T1>
struct MyContainer
{
T1 t1;
using _PCCP = _PCC<T1>;
template<class U1 = T1, typename
std::enable_if<_PCCP::template
ConstructibleFrom<U1>(),
bool>::type=true>
constexpr MyContainer(const T1& newT1) : t1(newT1) {}
};
template<typename T1> MyContainer(T1) -> MyContainer<T1>;
void someFunc() {};
int main()
{
auto c = MyContainer(someFunc);
}
What should happen here (I think) is that the constructor of MyContainer is not taken for CTAD. Taking the constructor for CTAD would lead to T1 being deduced as void(), because T1 is taken as a const reference in the constructor, which prevents function-to-pointer decay when deducing the template parameter for the constructor. But, if T1 is deduced as void(), the std::enable_if disables the constructor - so it sholud not be taken for deduction.
The deduction guide below remains, and since T1 is taken by value there, function-to-pointer decay happens and T1 is deduced as void(*)(). With T1 deduced as void(*)(), MyContainer should be fine, since a function pointer can of course be a member.
However, Clang reports this error:
<source>:17:8: error: data member instantiated with function type 'void ()'
T1 t1;
^
<source>:21:24: note: in instantiation of template class 'MyContainer<void ()>' requested here
std::enable_if<_PCCP::template
^
<source>:24:15: note: while substituting prior template arguments into non-type template parameter [with T1 = void (), U1 = void ()]
constexpr MyContainer(const T1& newT1) : t1(newT1) {}
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:34:14: note: while substituting deduced template arguments into function template '<deduction guide for MyContainer>' [with T1 = void (), U1 = (no value), $2 = (no value)]
auto c = MyContainer(someFunc);
^
1 error generated.
I assume that this error arises because Clang tries to instantiate MyContainer<void()> during CTAD, more precisely while checking if the std::enable_if is satisfied or not. The locations reported in the error point to that, and: If you remove the using declaration from the class and instead write the constructor like this, the error goes away:
template<class U1 = T1, typename
std::enable_if<_PCC<T1>::template
ConstructibleFrom<U1>(),
bool>::type=true>
constexpr MyContainer(const T1& newT1) : t1(newT1) {}
Note that this is the same constructor as above, I just manually replaced _PCCP with _PCC<T1> and removed the using.
I assume that Clang somehow needs to instantiate the class (which is impossible with T1 = void()) for accessing the using, which is a member of the class.
@llvm/issue-subscribers-clang-frontend
I have found a way easier example to reproduce the same problem (at least I think it's the same problem):
template<class T>
struct Foo {
using MyType = T;
template<std::enable_if_t<!std::is_same_v<MyType, int>, bool> = true>
Foo(T t, char c) {};
Foo(char i1, T i2) {};
};
int main()
{
char c = 42;
int i = 23;
auto x = Foo(i, c);
}
The interesting aspect here is that even if Clang needs to instantiate Foo<int> and Foo<char> to figure out the respective MyTypes, these classes should be perfectly valid.