【问题标题】:Deduction guides and variadic class templates with variadic template constructors - mismatched argument pack lengths带有可变参数模板构造函数的推导指南和可变参数类模板 - 参数包长度不匹配
【发布时间】:2017-04-15 20:24:25
【问题描述】:

考虑以下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++ 拒绝我的代码是否正确? 我的代码格式不正确吗?

【问题讨论】:

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


【解决方案1】:

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

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

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

【讨论】:

  • 考虑到std::tuple 有推导指南,我想说假设允许可变参数模板是很安全的。
  • 同意。报告为bug #80438
  • 是的,这是真的,所以我去寻找它为什么有效。元组使用variadic deduction guide。但是由于tuple constructor 中的 enable_if 导致推演指南不匹配
【解决方案2】:

由于 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&lt;&gt;。为所有编译器添加它是安全的,因为它在 1+ 参数情况下是首选的,而不是在 0 参数情况下的候选者。

Example.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-06
    • 2013-09-14
    • 2018-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    相关资源
    最近更新 更多