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

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

我有一个模板化的基础 class,它需要 N 种类型:

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

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

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

我想另外包含基础 class 的 public 构造函数:

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 没有模板构造函数,因此 using Base<Ts...>::Base<Ts...>; 正在尝试查找不存在的构造函数。

假设我有一个 class 喜欢 你的

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;.

您不需要类型别名。问题是 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:: 容器派生! 封装它们。

using Base<Ts...>::Base<Ts...>; 是非法的,因为 [namespace.udecl]p5:

中有一条非常普遍的规则

A using-declaration shall not name a template-id.

这与您不能在 using 声明中命名特定重载的事实非常相似。但是,我不知道这条规则的基本原理。


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

  • "Constructors do not have names." [class.ctor]p1
  • "The point of declaration of a using-declaration that does not name a constructor [...]" [class.ctor]p4

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

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

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


[class.qual]p2 指定如何在限定 ID 中命名构造函数(由 nested-name-specifier不合格的 id):

In a lookup in which function names are not ignored and the nested-name-specifier nominates a class C:

  • (2.1) if the name specified after the nested-name-specifier, when looked up in C, is the injected-class-name of C, or
  • (2.2) in a using-declaration that is a member-declaration, if the name specified after the nested-name-specifier is the same as the identifier or the simple-template-id’s template-name in the last component of the nested-name-specifier,

the name is instead considered to name the constructor of class C.

对于address/comment一些其他的回答,我会跑题了..

可以争辩说 (2.1) 不适用于 OP:虽然 Base 包含一个 injected-class-name,这个名字是Base 而不是 Base<Ts...>。在 nested-name-specifier 之后指定的名称是 Base<Ts...>,这不是 injected-class-name .

请注意,依赖基 class 的 injected-class-name 在实例化之前是未知的。考虑

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++ 接受它。这是CWG 2070

我们确实知道,在定义点,OP 中的 Base 是 class,因此可以 推断 注入-class-名字。这将需要区分名称在定义时已知的类型和名称未知的类型。别名模板可以隐藏这一点,因此这不是 using 声明中合格 ID 的 形式 的微不足道的区别。

无论如何,要点 (2.2) 不适用于 Base<Ts...>::Base<Ts...>:它的 nested-name-specifierBase<Ts...>模板名称Basenested-name-specifier 之后的名称是 Base<Ts...>.


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