推导指南和带有可变模板构造函数的可变 class 模板 - 不匹配的参数包长度
Deduction guides and variadic class templates with variadic template constructors - mismatched argument pack lengths
考虑以下 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
虽然 clang++ 肯定有问题 (报告为问题 #32673),但 g++ 拒绝我的代码是否正确? 我的代码格式不正确吗?
为了进一步简化您的示例,GCC 似乎没有在演绎指南中实现可变模板参数:
https://wandbox.org/permlink/4YsacnW9wYcoceDH
我在标准或 cppreference.com 上的推导指南措辞中没有看到任何明确提及可变参数模板。我看不到任何不允许这样做的标准解释。因此我认为这是一个错误。
由于 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 参数情况下的候选。
考虑以下 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
虽然 clang++ 肯定有问题 (报告为问题 #32673),但 g++ 拒绝我的代码是否正确? 我的代码格式不正确吗?
为了进一步简化您的示例,GCC 似乎没有在演绎指南中实现可变模板参数:
https://wandbox.org/permlink/4YsacnW9wYcoceDH
我在标准或 cppreference.com 上的推导指南措辞中没有看到任何明确提及可变参数模板。我看不到任何不允许这样做的标准解释。因此我认为这是一个错误。
由于 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 参数情况下的候选。