为什么在使用可变构造函数时必须创建类型别名

Why must I create a type alias when using a variadic constructor function?

本文关键字:创建 类型 别名 构造函数 为什么      更新时间:2023-10-16

我有一个模板化基类,它采用N种类型:

template <typename... Ts>
class Base{};

在该基类上使用受保护的继承时,

template <typename... Ts>
class Derived : protected Base<Ts...>{
   //like so...
};

我想另外包括基类的公共构造函数:

template <typename... Ts>
class Derived : protected Base<Ts...>{
   //create an alias
   using Parent = Base<Ts...>;
   //get all constructors as well
   using Parent::Parent; 
};

这是有效的
但是,为什么必须包含Parent别名?

似乎没有它我就无法获得构造函数。以下尝试不起作用:

template <typename... Ts>
class Derived : protected Base<Ts...>{
   //get all constructors as well
   using Base<Ts...>::Base<Ts...>; 
};

错误:

clang++ -std=c++1z -o main v.cpp
error: expected ';' after using declaration
       using Base<Ts...>::Base<Ts...>; 
                              ^
                              ;
1 error generated.

我可以剪掉模板部分,然后编译,但这似乎不正确:

template <typename... Ts>
class Derived : protected Base<Ts...>{
   //get all constructors as well
   using Base<Ts...>::Base; 
};

我认为它不正确的原因是它似乎对向量不起作用
不编译:

template <typename... Ts>
class Derived : protected std::vector<Ts...>{
   //get all constructors as well
   using std::vector<Ts...>::std::vector; 
};

但是,使用别名确实有效
编译:

template <typename... Ts>
class Derived : protected std::vector<Ts...>{
   //create an alias
   using Parent = std::vector<Ts...>;
   //get all constructors as well
   using Parent::Parent; 
};

问题:
我是否必须使用别名才能获得相同的功能,或者有没有一种方法可以在不为基类型创建新名称的情况下内联它?

您不需要类型别名。问题是Base<Ts...>的构造函数不被称为Base<Ts...>。它被称为Base

这项工作:

template<class...Ts>
struct Base {
};
template<class...Ts>
struct Derived : Base<Ts...>
{
    using Base<Ts...>::Base;
};

此外,std::vector<...>的构造函数的名称不是std::vector,而是vector

这也起作用:

template<class...Ts>
struct Derived : std::vector<Ts...>
{
    using std::vector<Ts...>::vector;
};

但是,不是从std::containers派生的封装它们。

在您的示例中,Base没有模板构造函数,因此using Base<Ts...>::Base<Ts...>;试图找到一个不存在的构造函数。

想象一下,我有一门课,就像你的

class Base{
    public:
        Base(){}
        template<typename ... Ts>
        Base(){}
};

using Base<Ts...>::Base<Ts...>会选择哪个构造函数?

using Parent = Base<Ts...>之所以有效,是因为在编写using Parent::Parent时,您试图找到一个而非模板化的Parent构造函数。它扩展到using Base<Ts...>::Base;

using Base<Ts...>::Base<Ts...>;是非法的,因为在[namespace.udcl]p5:中有一条非常通用的规则

使用声明的不应命名模板id

这与在使用声明时无法命名特定重载的情况非常相似。不过,我不知道这项规则的理据为何。


构造函数和名称之间的关系。。。很复杂:

  • "构造函数没有名称。"[class.ctor]p1
  • "使用不命名构造函数的声明的的声明点[…]"[class.ctor]p4

因此,虽然构造函数没有的名称,但它们可以命名为。对我来说,这似乎类似于匿名类型:

struct { int m; } x;
decltype(x) // refers to the type of `x` which has no name

应继承构造函数的using声明必须命名构造函数


[class.qual]p2指定如何在限定id中命名构造函数(由嵌套名称说明符非限定id组成):

在函数名不被忽略并且嵌套名称说明符命名类C:

  • (2.1)如果在C中查找时,在嵌套名称说明符之后指定的名称是C注入类名,或者
  • (2.2)在作为成员声明的using声明中,如果在嵌套名称说明符之后指定的名称与标识符简单模板id模板名称嵌套名称说明符的组件

该名称被认为是对类CCD_ 19的构造函数进行命名。

为了解决/评论其他一些答案,我会跑题一点。。

可以说(2.1)不适用于OP:虽然Base包含一个注入的类名,但这个名称是Base,而不是Base<Ts...>。在嵌套名称说明符之后指定的名称是Base<Ts...>,它不是注入的类名

注意,依赖基类的注入类名直到实例化时才知道。考虑

template<typename T>
struct foo : T {
    using T::name;
    void name(double);
};
struct name { name(int); };
struct bar { void name(int); };
foo<name> // constructor inheritance?
foo<bar>  // makes a function visible?

clang++拒绝这个例子,而g++接受它。这是CWG2070

然而,在定义时,我们确实知道OP中的Base是一个类,因此可以推断注入的类名。这需要区分在定义时名称已知的类型和不知道名称的类型。别名模板可以隐藏这一点,因此这不是using声明中限定id的形式的一个微不足道的区别。

在任何情况下,项目符号(2.2)都不适用于Base<Ts...>::Base<Ts...>:其嵌套名称说明符Base<Ts...>。此模板的模板名称Base嵌套名称说明符之后的名称为Base<Ts...>


[class.qual]p2.2允许使用Base<Ts...>::Baseparent::parent,只需查看限定id中使用的标识符即可。此处不需要执行实际的名称查找。表单本身足以命名构造函数。