列表初始化和可变参数构造函数

3
CPP reference 中了解到,关于列表初始化:

否则,T 的构造函数将被两个阶段考虑:

  • 检查所有只接受 std::initializer_list 作为唯一参数或者作为其余参数有默认值的首个参数的构造函数,并通过重载决策来与 std::initializer_list 类型的单一参数进行匹配。

  • 如果上一个阶段没有找到合适的构造函数,那么 T 的所有构造函数都参与重载决策。这里的重载决策是对列表初始化元素的构造函数进行调用,仅允许非缩窄转换。 如果在复制列表初始化期间找到显式构造函数,则编译失败(请注意,在简单的复制初始化中,不考虑显式构造函数)。

因此,首先考虑使用 initializer_list 的构造函数。如果失败,则将列表中的每个元素视为构造函数的参数。 然而

#include <iostream>

using namespace std;

struct A{
    template <typename... Args> A(Args... li) { cout << sizeof...(Args) << endl;}
};

int main(){

    A a = {2,3,4};

}

输出为3,这表明Args...被展开为int,int,int。为什么不直接使用单数形式的initializer_list<int>呢?详见列表初始化中的细节,它应该是第一个尝试的构造函数类型。
2个回答

4
[temp.deduct.call]/1 模板参数推导通过将每个函数模板参数类型(称其为P)与调用的相应参数的类型进行比较(称其为A),如下所述。如果从P中删除引用和cv限定符得到std::initializer_list<P'>,其中P'是某个类型,并且参数是初始化列表(8.5.4),则对于初始化列表的每个元素分别执行推导,将P'作为函数模板参数类型,将初始化元素作为其参数。否则,“初始化列表参数”会导致该参数被认为是非推导上下文(14.8.2.5)。

以上为强调内容。因此,使用initializer_list<int>类型的参数进行构造函数的模板参数推到会失败。


只是为了明确,initializer_list<int>参数将导致在A(Args...)中成功的模板参数推断。因此,我CPP引用的第一行(转换为initialiser_list参数)适用于查找候选非模板构造函数,而您的引用适用于使用模板参数推断查找候选模板函数。如果模板或非模板函数都没有提供合适的候选项,则将每个列表元素视为单独的参数。 - AntiElephant
[dcl.init.list]/2 中有一条注释:“将初始化器列表作为类C的构造函数模板template<class T> C(T)的参数传递不会创建一个初始化器列表构造函数,因为初始化器列表参数会导致相应的参数是一个无法推导的上下文(14.8.2.1)。“这就是我查阅了**[temp.deduct.call]**的原因。现在,将你程序中的 main 改为 std::initializer_list<int> l{2,3,4}; A a = l; 会编译通过。我不确定为什么。 - Igor Tandetnik
啊。**[temp.deduct.call]/1**讨论的是参数为初始化列表(即用花括号括起来的逗号分隔值序列)而不是std::initializer_list实例的情况。这种区别很重要,两者不能互换。 - Igor Tandetnik

1
如果您明确地提供了一个带有 std::initializer_list 的构造函数,则会选择它:Demotemplate <typename... Args> A(Args...) 不是一个带有第一个参数 std::initializer_list 的构造函数(即使第一个参数可以是一个 std::initializer_list)。
A a = {2, 3, 4} 中,{2, 3, 4} 没有类型。它不是一个 std::initializer_list

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接