为什么它是一个使用GCC的令人震惊的函数调用?模板扣减失败
Why is it an ambigious function call using GCC? Template deduction failing?
在我看来,我无法用GCC或clang编译当前有效的C++(17)代码。
我最近用clang编译了我(在我看来)的有效C++17代码,导致了一个错误(错误报告:https://bugs.llvm.org/show_bug.cgi?id=40305)。后来,我更改了代码,并在尝试使用GCC编译代码时出错。
我设法隔离了有问题的代码部分,并为这两个编译器找到了可能的解决方案,它们也适用于我的真实代码:
#include <iostream>
#include <utility>
template<class T, int... Ns>
class Tensor
{
};
template<class T, int... Ns>
class Base
{
};
template<class T, int... Ns>
class Derived : public Base<T, Ns...>
{
};
template<class T, int... Ns>
decltype(auto) convert(Base<T, Ns...> const &a)
{
return a;
}
template<class T, int... Ns>
auto convert(Tensor<T, Ns...> const &)
{
return Derived<T, Ns...>();
}
#ifdef WGCC1 // First work-around for GCC
template<class T, int... Ns>
void error(Base<T, Ns...> const &arg)
{
std::cout << "Function" << std::endl;
}
#endif
template<class... Ts, int... Ns>
void error(Base<Ts, Ns...> const &... args)
{
std::cout << "Function" << std::endl;
}
template<class... Ts
#ifdef WGCC2 // Second work-around for GCC
>
#else
,
class = std::enable_if_t<
std::conjunction<std::is_class<std::remove_reference_t<Ts>>...>{}>>
#endif
void error(Ts &&... args)
{
std::cout << "Wrapper: ";
((std::cout << typeid(args).name() << " "), ...);
std::cout << std::endl;
#ifdef WCLANG // Work-around for clang, see:
// https://bugs.llvm.org/show_bug.cgi?id=40305
return error(convert(convert(std::forward<Ts>(args)))...);
#else
return error(convert(std::forward<Ts>(args))...);
#endif
}
int main()
{
Tensor<int, 4, 4> a;
error(a);
}
请参阅:https://godbolt.org/z/L5XVgL
注意:很明显,这个代码已经没有意义了,即SFINAE检查std::is_class。然而,问题与我的、有意义的、真实的代码中的问题相同。
结果:
- 没有解决方法的叮当声会导致内部编译器错误
- 没有任何解决方法的GCC导致对的函数调用不明确
error(const Base<T, Ns...>&)
事实上,我预计不需要变通。clang应该能够推导出模板参数。我看不出GCC是如何得出函数调用不明确的想法的,在我看来这是非常清楚的。我是做错了什么,还是假设模板扣减是不符合C++(17)标准的?
其他意外行为:启用其中一个GCC解决方案后,将调用包装器函数两次。我不确定这是意料之中的事。或者换句话说:我不确定是先进行模板推导还是转换为基数。Clang在这里的观点似乎与GCC不同。也就是说,先转换到基准的引用,但之后模板推导失败(见错误报告)。哪一个是对的?
更新:GCC的已提交错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88907
-
让我们将gcc的问题简化为:
template<class... Ts, int... Ns> void error(Base<Ts, Ns...> const &... args) {} // #1 template<class... Ts, std::enable_if_t< std::conjunction<std::is_class<std::remove_reference_t<Ts>>...>{}, int> = 0> void error(Ts &&... args) {} #2 int main() { const Base<int, 4> b; error(b); // call #1 }
遵循overload_resolution 的(复杂)规则
我们将这两种方法都作为可行的函数,两者都是模板,所以我们使用更专业的模板
我(作为clang)理解#1比#2更专业,但不适用于gcc
演示
我会说gcc bug。
-
让我们简化clang to的问题:
template<class... Ts, int... Ns> void error(Base<Ts, Ns...> const &... args) {} // #1 template<class... Ts, std::enable_if_t< (... && std::is_class<std::remove_reference_t<Ts>>::value), int> = 0> void error(Ts&&... args) {} // #2 int main() { Derived<int, 4, 4> d; error(d); // call #2 }
ICE总是一个bug,所以叮当的bug。
对于分辨率,#2是一个精确匹配,而#1需要将派生类转换为其基类。
gcc同意调用#2。
演示
我不知道这对您来说是否可以接受,但我为您的原始问题提出了一个不同的解决方案:在模板-模板参数和容器中进行转换。
我的意思是。。。而不是
template <class... Ts, int... Ns>
void error(Base<Ts, Ns...> const &... arg)
{ }
我建议
template <template <typename, int...> class C, typename ... Ts, int ... Ns>
void error(C<Ts, Ns...> const &...)
{ }
如果你想确保C
是从Base
派生的,你可以通过SFINAE强制执行;使用C++17模板折叠,可以编写
template <template <typename, int...> class C, typename ... Ts, int ... Ns>
std::enable_if_t<(... && std::is_base_of_v<Base<Ts, Ns...>, C<Ts, Ns...>>)>
error(C<Ts, Ns...> const &...)
{ }
您还可以在模板模板的可变列表中转换C
,并接受Base
和Derived
的混合
template <template <typename, int...> class ... Cs, typename ... Ts, int ... Ns>
std::enable_if_t<(... && std::is_base_of_v<Base<Ts, Ns...>, Cs<Ts, Ns...>>)>
error(Cs<Ts, Ns...> const &...)
{ }
下面是一个完整的编译(g++和clang++)示例
#include <type_traits>
template <typename, int...>
class Base {};
template <typename, int...>
class Wrong {};
template <typename T, int... Ns>
class Derived : public Base<T, Ns...> {};
template <template <typename, int...> class ... Cs, typename ... Ts, int ... Ns>
std::enable_if_t<(... && std::is_base_of_v<Base<Ts, Ns...>, Cs<Ts, Ns...>>)>
error(Cs<Ts, Ns...> const &...)
{ }
int main ()
{
Base<int, 1, 2, 3> a;
Base<long, 1, 2, 3> b;
error(a, b);
Derived<int, 1, 2, 3> c;
Derived<long, 1, 2, 3> d;
error(c, d);
error(a, c, b, d);
Wrong<int, 1, 2, 3> e;
Wrong<long, 1, 2, 3> f;
//error(e, f); // compilation error
//error(a, c, e, b, d, f); // compilation error
}
- 函数调用中参数的顺序重要吗
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- 使用显式模板参数列表和 [temp.arg.explicit]/3 的函数调用的演绎失败
- 从 C#-DLL 调用函数的 C++ 失败
- std::tie 在从函数调用传递值时失败,并显示"无法绑定非常量左值引用"
- asio::thread_pool 在调用构造函数之前失败
- 为什么它是一个使用GCC的令人震惊的函数调用?模板扣减失败
- 超能力 NDK 安卓:返回 int 在"extern"块中成功,在调用函数中失败
- C++:转发模板成员函数调用失败
- 文件系统::p ath 构造函数调用失败
- 为什么这个C++成员函数调用会失败并出现分段错误
- CUDA 调用在析构函数中失败
- set_new_handler (std::new_handler func) 失败后的构造函数调用,用于内存分配失败
- 运行时检查失败 #0 - ESP 的值未在函数调用中正确保存
- C++ Android 上的代码 - execl() 函数调用失败
- 运算符new失败时的构造函数调用
- 重载的*运算符在多个*操作后调用析构函数时失败
- 为什么非虚拟函数调用即使在dynamic_cast失败后仍然成功
- 错误:没有匹配的函数调用[…]注意:模板参数演绎/替换失败
- C++复制初始化+隐式构造函数调用=失败