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

时间:2015-06-30 04:47:48

标签: c++ templates inheritance variadic-templates c++14

我有一个模板化的基类,需要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; 
};

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

3 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

在您的示例中,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;

答案 2 :(得分:1)

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

  

using-declaration 不得命名 template-id

这与使用声明中无法命名特定重载的事实非常类似。但是,我不知道这条规则的基本原理。

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

  • &#34;构造函数没有名称。&#34; [class.ctor] P1
  • &#34; using-declaration 的声明点,它没有命名构造函数[...]&#34; [class.ctor] P4

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

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

继承构造函数的 using-declaration 必须命名构造函数

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

  

在查找中,不忽略函数名称和    nested-name-specifier 指定一个类C

     
      
  • (2.1)如果在C中查找嵌套名称说明符之后指定的名称是的注入类名称 C
  •   
  • (2.2)在 using-declaration 中是 member-declaration ,如果在 nested-name-specifier 之后指定的名称是相同的   最后的标识符 simple-template-id 模板名称   嵌套名称说明符的组件,
  •   
     

而是将名称视为命名类C的构造函数。

为了解决/评论其他一些答案,我会稍微离开......

可以说(2.1)不适用于OP:虽然Base包含注入类名,但此名称为Base而不是{{ 1}}。 嵌套名称说明符之后指定的名称是Base<Ts...>,而不是 inject-class-name

请注意,在实例化时间之前,不知道依赖基类的注入类名。考虑

Base<Ts...>

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

我们确实知道,在定义时,OP中的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? 是一个类,因此可以推断 注入类名。这需要区分在定义点知道名称的类型和不在定义点的名称。别名模板可以隐藏它,因此这不是使用声明中qualified-id的 form 的微不足道的区别。

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

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