我最近遇到了一些代码,这些代码试图将函数(而不是函数指针(放入std::pair
中,GCC接受了这些代码,但Clang没有接受。这让我开始调查什么是";正确的";这里是根据C++(17(标准。考虑一下:
void foo(int) {}
// (1) Not allowed in GCC, Clang or MSVC
std::pair<decltype(foo), decltype(foo)> myFirstPair;
// (2) Compiles in GCC, fails in Clang and MSVC
auto myPair = std::pair{foo, foo};
// (3) Compiles in GCC, Clang and MSVC
auto myOtherPair = std::make_pair(foo, foo);
(也在编译器资源管理器中(
显然,不能将函数类型作为成员(这将是将std::pair<T1, T2>
的T1
或T2
之一推导为函数类型void ()(int)
的结果(,但函数指针是可以的。事实上,在编译成功的情况下似乎就是这样:对于(3)
,Clang和GCC都推导出myOtherPair
的类型std::pair<void (*)(int), void (*)(int)>
。对于(2)
,GCC还推导出myPair
的std::pair<void (*)(int), void (*)(int)>
。
我假设这里发生的是根据标准的[conv.func]
进行的函数到指针的转换:
7.3函数到指针的转换[conv.func]
函数类型为T的左值可以转换为类型为"指向T的指针"的prvalue。结果是指向函数的指针。
Clang、GCC和MSVC似乎都将该转换应用于std::make_pair
的参数,而只有GCC似乎将该转换适用于std::pair
的构造函数的参数。
由于标准只规定了转换可以发生(甚至没有说明在哪些情况下可以发生(,所以这三个编译器都正确吗?这只是标准中没有规定的东西吗?
我想我已经把它归结为Clang中的一个bug。这个答案在这里也可以作为Clang的bug使用。
考虑这个";最小";我从GCC的std::pair
的STL实现中浓缩的示例:
#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);
}
应该在这里发生的事情(我认为(是MyContainer的构造函数没有用于CTAD。采用CTAD的构造函数会导致T1
被推导为void()
,因为T1
在构造函数中被用作常量引用,这防止了在推导构造函数的模板参数时函数到指针的衰减。但是,如果T1
被推导为void()
,则std::enable_if
会禁用构造函数,因此它不会被用于推导。
下面的推导指南仍然存在,由于T1
是由那里的值取的,因此函数到指针的衰减发生,T1
被推导为void(*)()
。将T1
推导为void(*)()
,MyContainer
应该是好的,因为函数指针当然可以是成员。
然而,Clang报告了这个错误:
<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.
我认为出现此错误是因为Clang试图在CTAD期间实例化MyContainer<void()>
,更确切地说,是在检查std::enable_if
是否满足时。错误中报告的位置指向了这一点,并且:如果您从类中删除using声明,而是像这样编写构造函数,那么错误就会消失:
template<class U1 = T1, typename
std::enable_if<_PCC<T1>::template
ConstructibleFrom<U1>(),
bool>::type=true>
constexpr MyContainer(const T1& newT1) : t1(newT1) {}
注意,这是与上面相同的构造函数,我只是用_PCC<T1>
手动替换了_PCCP
,并删除了using
。我假设Clang需要以某种方式实例化类(这在T1 = void()
中是不可能的(来访问using,using是该类的成员。