推导指南和带有可变模板构造函数的可变 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{}};

live example on wandbox

虽然 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 参数情况下的候选。

Example.