具有可变参数模板构造函数的演绎指南和可变参数类模板 - 不匹配的参数包长度

时间:2017-04-15 20:24:25

标签: c++ c++17 template-deduction

考虑以下class定义和deduction guide

template <typename... Ts>
struct foo : Ts...
{
    template <typename... Us>
    foo(Us&&... us) : Ts{us}... { }
};

template <typename... Us>
foo(Us&&... us) -> foo<Us...>;

如果我尝试使用显式模板参数实例化foo,则代码会正确编译:

foo<bar> a{bar{}}; // ok

如果我尝试通过扣除指南来实例化foo ...

foo b{bar{}};
  • g ++ 7产生编译错误:

    prog.cc: In instantiation of 'foo<Ts>::foo(Us ...) [with Us = {bar}; Ts = {}]':
    prog.cc:15:16:   required from here
    prog.cc:5:27: error: mismatched argument pack lengths while expanding 'Ts'
         foo(Us... us) : Ts{us}... { }
                               ^~~
    
  • clang ++ 5爆炸:

    #0 0x0000000001944af4 PrintStackTraceSignalHandler(void*) (/opt/wandbox/clang-head/bin/clang-5.0+0x1944af4)
    #1 0x0000000001944dc6 SignalHandler(int) (/opt/wandbox/clang-head/bin/clang-5.0+0x1944dc6)
    #2 0x00007fafb639a390 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x11390)
    #3 0x0000000003015b30 clang::Decl::setDeclContext(clang::DeclContext*) (/opt/wandbox/clang-head/bin/clang-5.0+0x3015b30)
    ...
    clang-5.0: error: unable to execute command: Segmentation fault
    

live example on wandbox

虽然clang ++肯定是错误的(报告为问题#32673,但g ++在拒绝我的代码时是否正确? 我的代码格式不正确吗?

2 个答案:

答案 0 :(得分:7)

为了进一步简化您的示例,GCC似乎没有在演绎指南中实现可变参数模板参数:

https://wandbox.org/permlink/4YsacnW9wYcoceDH

我没有在标准或cppreference.com上的演绎指南的措辞中看到任何关于可变参数模板的明确提及。我看不到不允许这种标准的解释。因此我认为这是一个错误。

答案 1 :(得分:0)

由于foo具有构造函数,因此编译器generates an implicit deduction guide基于构造函数:

// implicitly generated from foo<T...>::foo<U...>(U...)
template<class... T, class... U> foo(U...) -> foo<T...>;

template<class... T> foo(T...) -> foo<T...>; // explicit

然后的问题是,gcc更喜欢隐式指南,因此将T推导为{},将U推导为{bar}; clang(自Godbolt以来为5.0.0)更喜欢显式指南。这是一个过载解决问题;当发现两个推导指南不明确时,请explicit deduction guides are preferred超过隐式推导指南。但是clang和gcc对于推论指南是否模棱两可表示分歧:

template<class... T, class... U> int f(U...) { return 1; }
template<class... T> int f(T...) { return 2; }
int i = f(1, 2);

该程序(根本不涉及推导)被gcc接受(选择#1),被clang拒绝(模棱两可)。追溯我们的步骤,这意味着要回到推论指南,clang通过选择隐式推论指南(由构造函数模板生成)上的显式推论指南来打破歧义,而gcc已选择了隐式推论,因此无法做到这一点。隐性推导指南作为首选。

我们可以构建一个更简单的示例:

template<class... T, int = 0> int f(T...);  // #1
template<class... T> int f(T...);  // #2
int i = f(1, 2);

同样,gcc(错误地)选择#1,而clang拒绝将其作为歧义。

重要的是,我们可以通过添加另一个显式演绎指南来解决方法,该显式演绎指南gcc仍将首选构造函数生成的隐式演绎指南:

template <typename U, typename... Us>
foo(U&& u, Us&&... us) -> foo<U, Us...>;

这是首选方法(当提供多个0参数时),因为它将第一个参数绑定到单个参数而不是包。在0参数的情况下,选择哪一个演绎指南(在原始显式指南和隐式生成的指南之间)都没有关系,因为两者都得出相同的结果foo<>。将其添加到所有编译器是安全的,因为在1+参数的情况下首选,而在0参数的情况下不是候选。

Example