为什么使用GCC进行模糊的函数调用?模板推导失败?

时间:2019-01-17 13:01:05

标签: c++ c++17 variadic-templates template-meta-programming template-deduction

我认为我无法使用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。但是问题与我有意义的真实代码中的问题相同。

结果:

  • 没有解决方法的clang会导致内部编译器错误。
  • 没有任何变通方法的GCC导致对函数的调用不明确
error(const Base<T, Ns...>&)

我实际上希望不需要任何解决方法。 clang应该能够推断出模板参数。 我看不到GCC如何提出函数调用是模棱两可的想法,我认为这很清楚。 我是在做错什么还是假设模板推演无法按照C ++(17)标准工作?

其他意外行为: 启用GCC解决方法之一后,包装函数将被调用两次。我不确定这是预期的。或者换句话说:我不确定模板推演或转换为基数是否首先完成。 lang似乎在这里与海湾合作委员会有不同的看法。即首先完成对基准的转换,但是之后模板推导失败(请参见错误报告)。哪个是对的?

更新:GCC的已归档错误:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88907

2 个答案:

答案 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

    Demo

    我会说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。

    Demo

答案 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,并接受BaseDerived的组合

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
 }