在不知道初始模板参数的情况下获取模板化结构

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

我正在寻找一种方法,从我正在构建的自定义 Node 类中获取指定的模板化结构,而无需在获取指针时进行 static_cast 或dynamic_cast。我可以用什么方法来避免这种情况?

节点.hpp

#pragma once

#ifndef NODE_H
#define NODE_H

#ifndef DATA_TEMPLATE
#define DATA_TEMPLATE template<typename T>
#else
#define DATA_TEMPLATE
#endif

#ifndef FUNCTION_TEMPLATE
#define FUNCTION_TEMPLATE template<typename R, typename... T>
#else
#define FUNCTION_TEMPLATE
#endif

#include <iostream>
#include <string>
#include <functional>
#include <ostream>
#include <memory>

struct Node {
    Node* prev;
    Node* next;

    Node(Node* prev = nullptr, Node* next = nullptr) : prev(prev), next(next) {}

    ~Node() = default;
    Node* getNext() const { return next; }
    void setNext(Node* next) { this->next = next; }
    Node* getPrev() const { return prev; }
    void setPrev(Node* prev) { this->prev = prev; }

    virtual void print(std::ostream& os) const = 0;
    friend std::ostream& operator<<(std::ostream& os, const Node& node) { node.print(os); return os; }
};

DATA_TEMPLATE
struct DataNode : public Node {
    T data;

    DataNode(Node* prev, T dat, Node* next) : Node{ prev, next }, data(dat) {}
    DataNode(Node* prev, T dat) : DataNode{ prev, dat, nullptr } {}
    DataNode(T dat, Node* next) : DataNode{ nullptr, dat, next } {}
    DataNode(T dat) : DataNode{ nullptr, dat, nullptr } {}


    T getData() { return data; }
    void setData(T newData) { data = newData; }

    void print(std::ostream& os) const override { os << data; }
    friend std::ostream& operator<<(std::ostream& os, const DataNode<T>& node) { node.print(os); return os; };
};

FUNCTION_TEMPLATE
struct FunctionNode : public Node {
    std::function<R(T...)> fn;

    FunctionNode(Node* prev, std::function<R(T...)> func, Node* next) : Node{ prev, next }, fn(func) {}
    FunctionNode(Node* prev, std::function<R(T...)> func) : FunctionNode{ prev, func, nullptr } {}
    FunctionNode(std::function<R(T...)> func, Node* next) : FunctionNode{ nullptr, func, next } {}
    FunctionNode(std::function<R(T...)> func) : FunctionNode{ nullptr, func, nullptr } {}

    void print(std::ostream& os) const override { os << "FunctionNode(" << fn.target_type().name() << ")"; }
    friend std::ostream& operator<<(std::ostream& os, const FunctionNode& node) { node.print(os); return os; }

    R operator()(T... args) { return fn(args...); }
};

#endif

主.cpp

// Includes ...

int main()
{
    DataNode<int> node{
        new DataNode<int>{ 7 },
        3,
        new DataNode<int>{ 5 }
    };

    std::cout << node << std::endl;
    

    auto* prevNode = node.getPrev();
    auto* nextNode = node.getNext();

    std::cout << *prevNode << std::endl;
    std::cout << *nextNode << std::endl;

    return 0;
}

我想要一个函数 Node 来输出它的结果。示例:

// Includes ...

int main()
{
    DataNode<int> node{
        new DataNode<int>{ 7 },
        3,
        new FunctionNode<int, int, int>(add) // add implemented elsewhere
    };

    std::cout << node << std::endl;
    

    auto* prevNode = node.getPrev();
    auto* nextNode = node.getNext(); // Should be a FunctionNode

    std::cout << *prevNode << std::endl;
    std::cout << (*nextNode)(2, 4) << std::endl;

    return 0;
}

预期输出:

3
7
6

编辑:我正在使用 ISO C++ 20 语言标准

编辑2:修复了DataNode的操作,似乎无法弄清楚运行FunctionNode。

c++ templates struct types
1个回答
0
投票

重申目标

据我了解这个问题,要点是你想要这条线:

    auto* nextNode = node.getNext(); // Should be a FunctionNode

推断

nextNode
具有类型
FunctionNode<int,int,int>*

为什么现在不起作用

在当前代码中,

nextNode
将被推导为具有类型
Node*
因为这是
Node::getNext()
的返回类型。 因此,需要进行类型转换(我们想要什么 避免)。

事实上,前面的代码行显然 用指向 a 的指针填充

next
字段
FunctionNode<int,int,int>
没有任何影响; C++语言 规则仅考虑相关行上
node
的类型。

异构递归数据类型

核心问题是你想创建一个 静态类型的异构 递归数据类型。 也就是说,

Node
是递归的,因为它的类型 引用自身(它包含一个指向
Node
的指针),并且异构 因为您希望每个节点的数据可能不同。 和 你要求它是静态类型的,这意味着编译器已经 知道每个级别的类型,而不必使用类型转换来 在适当的级别检索适当的类型。

为此,创建一个内部节点类模板,该模板接受 存储数据的类型及其所有子项的类型及其 儿童等。 这充其量是不方便的,但却是唯一的方法 让编译器知道每次数据的类型是什么 水平。

这是一个显示

int
float
的单链表的示例:

#include <iostream>

template <typename DATA>
struct Node {
  DATA m_data;

  Node(DATA const &data) : m_data(data) {}
};

template <typename DATA, typename CHILD>
struct InteriorNode : Node<DATA> {
  CHILD *m_child;

  InteriorNode(DATA data, CHILD *child)
    : Node<DATA>(data),
      m_child(child)
  {}
};

int main()
{
  // The type has to encode the data types at all levels, and therefore
  // the shape of the entire tree.
  InteriorNode<int, Node<float> > tree{
    5,
    new Node<float>{
      3.14
    }
  };

  // The fields have the proper types.
  std::cout << tree.m_data << "\n";
  std::cout << tree.m_child->m_data << "\n";
  return 0;
}

输出:

5
3.14

二叉树的扩展很简单,就像与其他树一起使用一样 类型(包括函数类型)。

双向链表

但是,示例中的名称

next
prev
表明 目的是创建一个双向链表。 示例中的方法 上面不起作用,但可以使用可变参数来完成 模板,使用整数索引来表示参数中的哪个元素 打包每个节点包含的内容。

示例:

#include <cassert>           // assert
#include <iostream>          // std::cout
#include <tuple>             // std::{tuple, tuple_element, ...}


// Get element `index` of `Ts`.
//
// https://stackoverflow.com/questions/20162903/template-parameter-packs-access-nth-type-and-nth-element
template <int index, typename... Ts>
using NthTypeOfPack =
  typename std::tuple_element<index, std::tuple<Ts...> >::type;

// Get the number of elements in `Ts`.
template <typename... Ts>
inline constexpr int packSize = std::tuple_size_v<std::tuple<Ts...> >;


// Has member `type` that equals `THEN` if `b` is true, `ELSE` otherwise.
template <bool b,
          typename THEN,
          typename ELSE>
struct ITE;

template <typename THEN,
          typename ELSE>
struct ITE<true, THEN, ELSE> {
  using type = THEN;
};

template <typename THEN,
          typename ELSE>
struct ITE<false, THEN, ELSE> {
  using type = ELSE;
};


// Statically typed heterogeneous doubly-linked list.
//
// `index` indicates which element of `DATAs` this node has.
//
template <int index,
          typename... DATAs>
struct Node {
public:      // types
  using MyData = NthTypeOfPack<index, DATAs...>;

  using PrevNode =
    typename ITE<(index > 0),
                 Node<index-1, DATAs...>,
                 void>                           ::type;

  using NextNode =
    typename ITE<(index+1 < packSize<DATAs...>),
                 Node<index+1, DATAs...>,
                 void>                           ::type;

public:      // data
  MyData m_data;
  PrevNode *m_prev;
  NextNode *m_next;

public:      // methods
  Node(MyData data, NextNode *next = nullptr)
    : m_data(data),
      m_prev(nullptr),
      m_next(next)
  {
    // We need `if constexpr` because the code inside it will not
    // compile when `index+1` equals the pack size.
    if constexpr (index+1 < packSize<DATAs...>) {
      if (m_next) {
        m_next->m_prev = this;
      }
    }
  }
};


int main()
{
  Node<0, int, float, char> list{
    5,
    new Node<1, int, float, char>{
      3.14,
      new Node<2, int, float, char>{
        'x'
      }
    }
  };

  // The data fields have the proper types.
  std::cout << list.m_data << "\n";
  std::cout << list.m_next->m_data << "\n";
  std::cout << list.m_next->m_next->m_data << "\n";

  // Check the list links.
  assert(list.m_prev == nullptr);
  assert(list.m_next->m_prev == &list);
  assert(list.m_next->m_next->m_prev == list.m_next);
  assert(list.m_next->m_next->m_next == nullptr);

  // NOTE: The memory for the second and third list elements is leaked!

  return 0;
}

输出:

5
3.14
x

我不建议实际使用这种设计,因为它看起来很 不方便。 但我认为这是问题的答案。

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