当前位置: 首页 > 知识库问答 >
问题:

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

姚德容
2023-03-14

请考虑以下定义和演绎指南:

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

如果我试图通过演绎指南实例化< code>foo...

foo b{bar{}};

>

  • G7产生编译器错误:

    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
    

    wandbox上的实例

    虽然clang肯定被窃听了(报告为问题#32673),但g拒绝我的代码是正确的吗?我的代码格式错误吗?

  • 共有2个答案

    甄煜
    2023-03-14

    由于foo有一个构造函数,编译器会根据构造函数生成一个隐式演绎指南:

    // 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开始)更喜欢显式指南。这是一个过载解决问题;当发现两个演绎指南不明确时,显式演绎指南优于隐式演绎指南。但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

    实例

    罗学真
    2023-03-14

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

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

    我没有在标准或 cppreference.com 的扣除指南的措辞中看到任何明确提及可变参数模板的内容。我没有看到对不允许这样做的标准的解释。因此,我认为这是一个错误。

     类似资料:
    • 假设有一个模板函数 接受任意数量的参数。给定最后一个参数始终是 ,我如何实现下面显示的 模板,以便 包含此 的参数? 例如,如果像这样调用,应该是: 我的第一个想法是: 但这不会编译: 问题一: 为什么这不能编译?为什么模板参数演绎失败? 最终,我想出了这个解决方案: 这里,是,即中的最后一种类型。然后,的临时值被传递到,其中。这行得通,但在我看来很难看。 问题二: 我想知道如果没有两个函数和的临

    • 考虑以下代码: 失败的行在G7下返回以下错误: 这是正常的还是编译器错误?

    • 在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。可变参数模板的加入使得C++11的功能变得更加强大,而由此也带来了许多神奇的用法。 可变参数模板 可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号...: template<ty

    • 以这种方式传递参数有问题,我得到以下错误:“std::thread::thread”:没有重载函数接受4个参数。我怎么能这么做?

    • 以下是我的代码:< br > 一开始我只是重载了函数,发现有很多类似的代码。所以我正在考虑使用可变参数模板来获得更好的设计。(如果两个重载函数相似,如何做出更好的设计) 但是我不知道为什么会有错误:< br > main.cpp:27: 8:错误:没有匹配函数调用'getChar'ch=getChar(1, std::forward(str)...); 主要的cpp:37:2:注意:在函数模板专门

    • 受这个答案的启发,我生成了这段代码,其输出取决于编译器: 如果使用 GCC 11 编译,调用