Luk*_*rth 11 c++ language-lawyer c++17
我最近遇到过尝试将函数(而不是函数指针)放入 中的代码std::pair,该代码被 GCC 接受,但不被 Clang 接受。这导致我根据 C++ (17) 标准调查什么是“正确的”。考虑一下:
void foo(int) {}\n\n// (1) Not allowed in GCC, Clang or MSVC\nstd::pair<decltype(foo), decltype(foo)> myFirstPair;\n\n// (2) Compiles in GCC, fails in Clang and MSVC\nauto myPair = std::pair{foo, foo};\n\n// (3) Compiles in GCC, Clang and MSVC\nauto myOtherPair = std::make_pair(foo, foo);\nRun Code Online (Sandbox Code Playgroud)\n(也在编译器资源管理器中)
\n显然,您不能将函数类型作为成员(这将是推导出函数类型之一T1或T2之一的结果),但函数指针很好。事实上,这似乎就是编译成功的情况下发生的情况:对于,Clang 和 GCC 都推导了的类型。对于,GCC 也推论为。std::pair<T1, T2>void ()(int)(3)std::pair<void (*)(int), void (*)(int)>myOtherPair(2)std::pair<void (*)(int), void (*)(int)>myPair
我假设这里发生的是按照[conv.func]标准的函数到指针的转换:
\n\n7.3 函数到指针的转换[conv.func]
\n函数类型 T 的左值可以转换为指向 T\xe2\x80\x9d 的指针类型 \xe2\x80\x9c 的纯右值。结果是指向该函数的指针。
\n
Clang、GCC 和 MSVC 似乎都将该转换应用于 的参数std::make_pair,并且只有 GCC 似乎将该转换应用于std::pair\ 的构造函数的参数。
由于标准仅指定可以发生转换(甚至没有说明在什么情况下可以发生转换),所以三个编译器都正确吗?这只是标准中未指定的内容吗?
\n我想我已经将其归结为 Clang 中的一个错误。这个答案也可以作为针对 Clang 的错误在这里找到。
考虑一下我从 GCC 的 STL 实现中浓缩的这个“最小”示例std::pair:
#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);
}
Run Code Online (Sandbox Code Playgroud)
这里应该发生的情况(我认为)是 MyContainer 的构造函数不用于 CTAD。采用 CTAD 的构造函数将导致T1被推导为void(),因为T1被视为构造函数中的 const 引用,这可以防止在推导构造函数的模板参数时出现函数到指针的衰减。但是,如果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.
Run Code Online (Sandbox Code Playgroud)
我认为出现此错误是因为 Clang 尝试MyContainer<void()> 在 CTAD 期间实例化,更准确地说是在检查是否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) {}
Run Code Online (Sandbox Code Playgroud)
请注意,这与上面的构造函数相同,我只是手动替换_PCCP为_PCC<T1>并删除了using. 我假设 Clang 以某种方式需要实例化该类(这是不可能的T1 = void())来访问 using,它是该类的成员。