【问题标题】:Why must I create a type alias when using a variadic constructor function?为什么在使用可变参数构造函数时必须创建类型别名?
【发布时间】:2015-06-30 04:47:47
【问题描述】:

我有一个模板基类,它接受 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; 
};

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

【问题讨论】:

  • std::vector&lt;Ts...&gt;::std::vector 不应该在 using 语句中工作。
  • @CoffeeandCode 对,它不适用于矢量。虽然它可以编译为其他类型,但我怀疑它与使用 Alias::Alias 不同。
  • 不,我的意思是应该是namespace::type::type 而不是namespace::type::namespace::type
  • 哦,我明白了。是的,现在排除命名空间给了我与以前相同的错误(没有向量的示例,其中 clang 期望冒号早于它得到一个)。至少现在编译器错误是一致的。
  • 构造函数是 not Base&lt;Ts...&gt; 因为构造函数没有模板化。 using Base&lt;Ts...&gt;::Base; 是正确的写法。

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


【解决方案1】:

您不需要类型别名。问题是Base&lt;Ts...&gt; 的构造函数没有被称为Base&lt;Ts...&gt;。它叫做Base

这行得通:

template<class...Ts>
struct Base {

};

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

另外std::vector&lt;...&gt;的构造函数的名字不是std::vector,而是vector

这也有效:

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

但是,不要从 std:: 容器派生!封装它们。

【讨论】:

  • 这正是我的回答所说的。我什至举了一个例子,说明不同的名字很重要。
  • constructorsnames 之间的关系有点奇怪——我不得不承认我没有完全理解它。标准中的一段说构造函数没有名称,但另一段说构造函数可以在名称查找中找到。 -- 关于 OP 的问题,Base&lt;Ts...&gt; 可用于声明构造函数:coliru.stacked-crooked.com/a/fdbf380a0ca21f0a(尽管在定义它时不接受它的外线coliru.stacked-crooked.com/a/bc9cc0347a8f24c2
  • 也许你是对的:Base&lt;Ts...&gt; 没有命名构造函数,并且在类体内声明构造函数有一个特殊的例外,它允许使用 类名(可以是simple-template-id)。
【解决方案2】:

在您的示例中,Base 没有模板构造函数,因此 using Base&lt;Ts...&gt;::Base&lt;Ts...&gt;; 正在尝试查找不存在的构造函数。

想象一下我有一门课你的

class Base{
    public:
        Base(){}

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

using Base&lt;Ts...&gt;::Base&lt;Ts...&gt; 会选择哪个构造函数?

using Parent = Base&lt;Ts...&gt; 起作用的原因是,当您编写using Parent::Parent 时,您试图找到一个Parent 的构造函数,该构造函数没有 模板化。它扩展为using Base&lt;Ts...&gt;::Base;

【讨论】:

  • 虽然这个答案看起来正确,但事实并非如此。原因是基类的构造函数的名字不是Base&lt;Ts...&gt;。它是Base。看我的回答。
  • 别名 Parent::Parent 起作用的原因是因为在 A::B 的情况下,A 是 type 而 B 是 name我>。所以 Parent::Parent 意味着使用 type Parent 中的 name Parent.
  • "using Base::Base; 试图找到一个不存在的构造函数" 这要么不正确,要么误导:即使你添加一个构造函数模板并尝试通过 using 声明来引入它,编译器也会报错:coliru.stacked-crooked.com/a/bb192e1f99fdd3c7 I think Base&lt;Ts...&gt;::Base&lt;Ts...&gt; is not a legal qualified-id since Base 依赖。
  • 耸耸肩 在 [namespace.udecl]p5 中明确禁止:“_using-declaration_ 不得命名 _template-id_。”
【解决方案3】:

using Base&lt;Ts...&gt;::Base&lt;Ts...&gt;; 是非法的,因为 [namespace.udecl]p5 中有一条非常普遍的规则:

using-declaration 不得命名 template-id

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


构造函数和名字的关系……很复杂:

  • “构造函数没有名称。” [class.ctor]p1
  • using-declaration 的声明点没有命名构造函数 [...]”[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-ids 中命名(由一个nested-name-specifier 和一个unqualified-id 组成):

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

  • (2.1) 如果在 nested-name-specifier 之后指定的名称,当在 C 中查找时,是 @987654326 的 injected-class-name @,或
  • (2.2) 在作为 member-declarationusing-declaration 中,如果在 nested-name-specifier 之后指定的名称是相同的 最后的 identifiersimple-template-idtemplate-name 嵌套名称说明符的组件,

该名称被认为是命名类C的构造函数。

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

可以说 (2.1) 不适用于 OP:虽然 Base 包含 injected-class-name,但这个名称是 Base 而不是 Base&lt;Ts...&gt;nested-name-specifier之后指定的名字是Base&lt;Ts...&gt;,不是injected-class-name

请注意,依赖基类的 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 是一个类,因此可以推断 injected-class-name。这将需要区分在定义时名称已知的类型和不知道名称的类型。别名模板可以隐藏这一点,因此这不是 using-declaration 中qualified-id的form的微不足道的区别。

无论如何,要点 (2.2) 不适用于 Base&lt;Ts...&gt;::Base&lt;Ts...&gt;:它的 nested-name-specifierBase&lt;Ts...&gt;。它的模板名称Basenested-name-specifier 后面的名字是Base&lt;Ts...&gt;


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-27
    • 2016-03-26
    • 1970-01-01
    • 1970-01-01
    • 2020-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多