我正在寻找一种方法,从我正在构建的自定义 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。
据我了解这个问题,要点是你想要这条线:
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
我不建议实际使用这种设计,因为它看起来很 不方便。 但我认为这是问题的答案。