C++编译器的区别:将函数放入std::对中



我最近遇到了一些代码,这些代码试图将函数(而不是函数指针(放入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>T1T2之一推导为函数类型void ()(int)的结果(,但函数指针是可以的。事实上,在编译成功的情况下似乎就是这样:对于(3),Clang和GCC都推导出myOtherPair的类型std::pair<void (*)(int), void (*)(int)>。对于(2),GCC还推导出myPairstd::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是该类的成员。

最新更新