可变参数类模板和继承 - 默认编译器生成的构造函数

5
为什么下面的代码可以只使用默认编译器生成的构造函数就能运行? 我本来以为它只适用于POD,但下面的结构体可能不是POD,所以肯定还有其他原因。
template <typename ... T>
struct C : T ... {
   using T::operator()...;
};

// template class guidance feature of C++17
template <typename ... T>
C(T...) -> C<T...>;

int main(){
   C c { []{}, [](int){} };
   c(3);
}

这个问题是关于Jason的Turner C++周报第49/50期的跟进,他在其中使用了带有std::forward<T>(t)...的可变参数构造函数。

“POD”在C++中已经不再是一个真正的概念了。 - Kerrek SB
3
C 是一个符合 C++17 规范的聚合体。 - T.C.
谢谢!我不知道有这个新的C++17。 - Kobi
1
@Kerrek SB 不是这样的,std::is_pod<C>()仍然在起作用。但是pod假设了很多东西,一开始就变得微不足道了...那不是一个微不足道的类。 - Swift - Friday Pie
1个回答

7
这里没有使用构造函数。这段代码之所以能够运行是因为C++17中有三个新特性的结合:

  1. 模板参数推导用于构造函数 (P0091)。
  2. 扩展聚合初始化 (P0017)
  3. 现代化的using-declarations (P0195)。

这行代码发生了什么:

C c { []{}, [](int){} };

首先,我们使用模板参数推导(1)来推断出c实际上是C<__lambda1, __lambda2>类型。这是通过使用您的推断指南完成的。

接下来,由于C<__lambda1,__lambda2>是一个聚合体(由于(2)放宽了基类限制 - 您正确,它在C++11 / 14中不被视为聚合体),因此我们可以使用聚合初始化进行初始化。我们不使用构造函数。现在,聚合初始化对于基类的工作方式是只需从左到右初始化基类。因此,第一个表达式([] {})用于初始化第一个基类(__lambda1 ),第二个表达式([](int){})用于初始化第二个基类(__lambda2)。

最后,调用c(3)之所以有效,是因为(3)允许您简单地编写

using T::operator()...;

这将会把两个lambda的调用运算符引入到C的作用域中,使得重载决议可以如预期般工作。结果是我们调用了__lambda2的调用运算符,它什么也没做。


如果我在C中有一个成员,比如int i;,那么整个程序会崩溃,对吗?我无法初始化B...还有C的int i;成员。我的理解正确吗? - Kobi
我认为这样做是这样的,例如:C c { []{}, [](int){}, 1 };。您可以在初始化程序中在基础之后初始化聚合成员。请参见P0017r1中的“作用域”部分。 - TBBle

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