类模板的协变返回类型无效

问题描述 投票:0回答:1

这可能是一个很长的阅读,提前抱歉。这是我一直在从事的个人编译器项目。采用 EBNF 语法并将其编译为 C++、flex 和 bison 代码,该代码会自动为所描述的语法构建 AST。

我使用继承来对生成的 AST 进行建模,这导致我使用指针。我正在开发一个不可为空的智能指针类,其继承类似于 AST。

例如,如果我有这个 AST 继承模型:

Node
├── Terminal
└── Nonterminal
    ├── Expression
    │   ├── Operator
    │   │   ├── Binary
    │   │   │   ├── Add
    │   │   │   └── Multiply
    │   │   └── Unary
    │   └── FunctionCall
    └── Statement
        └── Conditional
            ├── Loop
            │   ├── For
            │   └── While
            ├── Select
            └── If

智能指针类继承模型如下所示:

Pointer<Node>
├── Pointer<Terminal>
└── Pointer<Nonterminal>
    ├── Pointer<Expression>
    │   ├── Pointer<Operator>
    │   │   ├── Pointer<Binary>
    │   │   │   ├── Pointer<Add>
    │   │   │   └── Pointer<Multiply>
    │   │   └── Pointer<Unary>
    │   └── Pointer<FunctionCall>
    └── Pointer<Statement>
        └── Pointer<Conditional>
            ├── Pointer<Loop>
            │   ├── Pointer<For>
            │   └── Pointer<While>
            ├── Pointer<Select>
            └── Pointer<If>

我的目标是为每种类型的 AST 节点提供一个虚拟克隆方法,该方法返回指向其自身类型的智能指针。我想使用这些方法来制作复制构造函数。二进制表达式(例如

Add
)将有两个操作数,两者都是
Pointer<Expression>
。为了定义
Add
的复制构造函数,我需要能够复制虚拟绑定的
Expression
,或任何与此相关的
Node
。类
Node
将定义一个虚拟方法,如下所示:

class Node {
public:
  virtual Pointer<Node> clone() const = 0;
};

它将像这样被覆盖:

class Token : public Node {
public:
  Pointer<Token> clone() override;
};

所以我可以这样做:

int main() {
  Pointer<Token> token = Pointer<Token>::New();
  Pointer<Node> tokenDynamic = new Pointer<Token>::New();

  Pointer<Token> tokenClone = token.clone();
  Pointer<Node> tokenDynamicClone = tokenDynamic.clone();
}

我遇到了一个问题。

我已经尝试尽可能简化这个例子,但我仍然无法让它工作:

#include <memory>
#include <tr2/type_traits>

class Base;
class Derived;

template<typename T>
class Template : public Template<typename T::Parent> {};

template<>
class Template<Base> {};

class Base {
public:
  virtual std::string name() const { return "Node"; }
  virtual Template<Base> clone() const = 0;
};

class Derived : public Base {
public:
  using Parent = Base;

  std::string name() const { return "Token"; }
  Template<Derived> clone() const override { return Template<Derived>(); }
};

template<typename T>
using base_t = typename std::tr2::bases<T>::type::first::type;

int main()
{
  auto base = Template<Base>();
  auto derived = Template<Derived>();
  base = Template<Derived>();
  static_assert(std::is_same_v<Template<Base>, base_t<Template<Derived>>>);
}

我认为虚函数可以被协变/缩小返回类型覆盖。上面代码中的

static_assert
并没有失败,这意味着
Template<Derived>
确实继承了
Template<Base>
。但是,编译器失败并显示以下消息:

./test2.cpp:24:21: error: invalid covariant return type for ‘virtual Template<Derived> Derived::clone() const’
   24 |   Template<Derived> clone() const override { return Template<Derived>(); }
      |                     ^~~~~
./test2.cpp:16:26: note: overridden function is ‘virtual Template<Base> Base::clone() const’
   16 |   virtual Template<Base> clone() const = 0;
      |                          ^~~~~

我已经无计可施了。如果有人能指出我正确的方向,我将不胜感激。感谢您阅读本文!

c++ templates abstract-syntax-tree covariance
1个回答
0
投票

只有返回(内置)指针或引用的函数才可能具有协变返回类型。

[class.virtual] 如果函数 D::f 覆盖函数 B::f,则返回类型 如果满足以下条件,则函数是协变的:

  • (8.1) — 两者都是指向类的指针,两者都是对类的左值引用,或者两者都是对类的右值引用 课程

然而,并非一切都丢失了。可以使用非虚拟接口惯用法来模拟协方差。该技术在几个 SO 问题和答案中都有描述,因此我不会在这里重复。有关更多信息,请参阅

在一个不相关的注释中,让智能指针相互继承可能不是一个好主意。如果您只需要协变返回类型,则可以安全地摆脱这种继承,甚至可能摆脱整个智能指针模板,并使用

std::shared_ptr
std::unique_ptr
代替。

© www.soinside.com 2019 - 2024. All rights reserved.