【问题标题】:Deduction guide for variadic template constructor fails可变参数模板构造函数的推导指南失败
【发布时间】:2025-12-21 13:10:06
【问题描述】:

我试图重现视频C++ Weekly - Ep 48 - C++17's Variadic using 的结果,但失败了。问题可以简化为如下代码段。

假设我有这样的通用结构:

template <class... T>
struct Container {
    template <class... U>
    Container(U... us) {}
};

现在我可以使用任何参数初始化Container,例如

auto d = Container(1,2,3);

但是,编译器永远不会知道d 是什么类型。为了解决这个问题,我们应该提供一个扣除指南,例如

template <class... U>
Container(U...) -> Container<double, int, bool>

根据视频,编译器现在应该知道d 的类型为Container&lt;double, int, bool&gt;

但是,代码没有按预期工作。打印typeid(d).name()时,输出永远是9ContainerIJEE,翻译成Container&lt;&gt;,不管我在推导指南里怎么改返回类型,说明这样的指南根本不指导编译器。

我用的是gcc-7-snapshot-20170402,视频中的编译器是gcc-7-snapshot-20170130

谁能告诉我这里出了什么问题?

更新:

顺便说一句,如果我明确写

Container<bool, int> d = Container(1,2,3);
Container<char, char, char> d = Container(1,2,3);
...

代码将始终编译,并提供像 9containerIJbiEE 9containerIJcccEE 这样的输出。

【问题讨论】:

  • 对我来说看起来像一个 gcc 错误。 9ContainerIJEE 不包含任何信息,但尝试为每个单独的模板参数打印 typeid().name()。

标签: c++ gcc c++17


【解决方案1】:

现在我可以用任何参数初始化一个容器,比如

auto d = Container(1,2,3);

但是,编译器永远不会知道d 是什么类型。

这并不完全准确。 d 的类型在这里是Container&lt;&gt;。让我们从构造函数的模板推导转移到简单的函数模板:

template <class... Ts, class... Us>
void foo(Us... );

foo(1, 2, 3);

该函数调用完全没问题——我们将Us 推断为{int,int,int},并将Ts 推断为{}。那是因为[temp.arg.explicit]/3

一个尾随模板参数包没有被推导出来,将被推导出为一个空的模板参数序列。如果所有的模板参数都可以推导出来,那么它们都可以被省略;在这种情况下,空模板参数列表&lt;&gt; 本身也可以省略。

现在,什么是 trailing 模板参数包?这是未指定的。但是这里Ts... 可以推断为空,所以它是。请注意,这有一些奇怪的含义,例如:

template <class... Ts> void g(std::tuple<Ts...> );
g({}); // ok, calls g<>?

另见this brief discussion


回到最初的问题。声明

Container d(1, 2, 3);

没有任何推导指南是格式良好的,因为我们可以成功地进行模板推导并将Ts...推导为空。也就是说,它完全等同于:

Container<> d(1, 2, 3);

那么当我们添加一个扣除指南时会发生什么?现在,我们在以下之间有效地执行重载解析:

template <class... Ts, class... Us>
Container<Ts...> f(Us... );             // the constructor template

template <class... Us>
Container<double, int, bool> f(Us... ); // the deduction guide 

f(1, 2, 3);                             // what does this return?

确定最佳可行候选人的决胜局在[over.match.best],其中相关的两个是:

鉴于这些定义,一个可行的函数F1 被定义为比另一个可行的函数F2更好 函数如果 [...]

  • F1F2 是函数模板特化,根据 [temp.func.order] 中描述的偏序规则,F1 的函数模板比​​ F2 的模板更特化,或者,如果不是那,
  • F1 是从演绎指南 ([over.match.class.deduct]) 生成的,而 F2 不是,或者,如果不是,[...]

函数模板部分排序 is really quite complicated 并且编译器在所有情况下都不能完全同意。但在这种情况下,我会说这是一个 gcc 错误(我提交了80871)。根据[temp.deduct.partial]

用于确定排序的类型取决于完成部分排序的上下文:

  • 在函数调用的上下文中,使用的类型是函数调用具有参数的函数参数类型。

也就是说,第一个函数模板(从构造函数合成的模板)中的Ts... 不用于偏序。这使得两个函数模板相同,因此没有一个比另一个更专业。我们应该然后进入下一个项目符号,它告诉我们更喜欢推导指南而不是构造函数,最终得到Container&lt;double, int, bool&gt;。然而,出于某种原因,gcc 认为第一个函数模板更专业,因此在进入演绎指南决胜局之前选择了它,这就是它以Container&lt;&gt; 结尾的原因。

【讨论】: