我认为我无法使用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。但是问题与我有意义的真实代码中的问题相同。
结果:
error(const Base<T, Ns...>&)
我实际上希望不需要任何解决方法。 clang应该能够推断出模板参数。 我看不到GCC如何提出函数调用是模棱两可的想法,我认为这很清楚。 我是在做错什么还是假设模板推演无法按照C ++(17)标准工作?
其他意外行为: 启用GCC解决方法之一后,包装函数将被调用两次。我不确定这是预期的。或者换句话说:我不确定模板推演或转换为基数是否首先完成。 lang似乎在这里与海湾合作委员会有不同的看法。即首先完成对基准的转换,但是之后模板推导失败(请参见错误报告)。哪个是对的?
更新:GCC的已归档错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88907
答案 0 :(得分:1)
让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(复杂的规则)
我们将两种方法都视为可行的功能, 两者都是模板,因此我们使用the more specialized template
我(作为叮当声)知道#1比#2更专业,但不适用于gcc
我会说gcc错误。
让clang的问题简化为:
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始终是一个错误,所以是clang的错误。
对于分辨率,#2是完全匹配,而#1需要将派生类转换为其基类。
gcc同意调用#2。
答案 1 :(得分:0)
我不知道您是否可以接受它,但针对您的原始问题,我提出了另一种解决方法:在template-template参数和容器中进行转换。
我的意思是...而不是
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
}