[英]Linked list with compile time checks in C++
我正在尝试实现一个带有编译时检查输入和输出功能的链接列表。 这些能力(或上限)应提供有效链接是否可以发生的信息。 因此,一个元素的外大写字母与下一个元素的内大写字母相交。 如果可以找到有效的交叉点,则建立链接。 请参阅提供的代码以在运行时进行此类检查。
虽然这在运行时很容易完成,但我不知道元/模板编程在编译时处理它的方向。 我已经开始阅读 std::variant<>、std::visit<>、模板实例化、模板专业化。
基本上,我了解元/模板编程,但仅限于 C++11 之前的级别。 请让我问这个悬而未决的问题,元/模板编程如何以及是否可行。
提前致谢。
#include <assert.h>
#include <list>
enum class NodeType {
Type1,
Type2,
Type3
};
class Caps {
public:
NodeType type;
int minValue;
int maxValue;
};
bool intersect(const std::list<Caps>& in, const std::list<Caps>& out)
{
for (auto i : in) {
for (auto o : out) {
if (i.type == o.type) {
auto minValue = std::max(i.minValue, o.minValue);
auto maxValue = std::min(i.maxValue, o.maxValue);
if (maxValue >= minValue) {
return true;
}
}
}
}
return false;
}
class Node
{
public:
virtual bool link(Node& next) {
if (intersect(this->outCaps(), next.inCaps())) {
m_next = &next;
return true;
}
return false;
}
virtual std::list<Caps> inCaps() { return {}; }
virtual std::list<Caps> outCaps() { return {}; }
protected:
Node* m_next = nullptr;
};
class DerivedNode1 : public Node
{
public:
std::list<Caps> outCaps() override {
return { { NodeType::Type1, 1, 10 },
{ NodeType::Type2, 5, 20 } };
}
};
class DerivedNode2 : public Node
{
std::list<Caps> inCaps() override { return { { NodeType::Type1, 8, 12 } }; }
};
class DerivedNode3 : public Node
{
std::list<Caps> inCaps() override { return { { NodeType::Type2, 1, 4 } }; }
};
class DerivedNode4 : public Node
{
std::list<Caps> inCaps() override { return { { NodeType::Type3, 1, 99 } }; }
};
int main()
{
DerivedNode1 node1;
DerivedNode2 node2;
DerivedNode3 node3;
DerivedNode4 node4;
assert(node1.link(node2)); // This shall link due to matching types and ranges
assert(!node1.link(node3)); // This shall fail due to non-matching range for a specific type
assert(!node1.link(node4)); // This shall fail due to non-matching types
}
下面做你可能想要的。 如果您不喜欢Link
class,可以将Node::link2
重命名为Node::link
,否则它需要具有不同的名称。
您可以根据自己的喜好使用Node::link2(a, b)
或a.link(b)
语法。 前者不需要将单参数link
方法注入派生类,因此可能更可取。 后者需要更多的工作才能进行深度推导
// Wouldn't work because `DerivedNodeY::link` and `DerivedNodeX::link` are ambiguous;
class DerivedNodeX : public DerivedNodeY, Link<DerivedNodeX>
{
public:
static constexpr std::array<Caps,1> inCaps() { ... }
// but this makes it work:
using Link<DerivedNodeX>::link;
};
如果没有Link
class,派生节点看起来就像:
class DerivedNode : public Node
{
public:
static constexpr std::array<Caps,1> inCaps() {
return {{ { NodeType::Type3, 1, 99 } }};
}
};
代码是 C++17,它与 gcc 和 clang 一起编译,但是它使 MSVC(最高 19.22)崩溃,并出现一个不错的编译器测试用例:(。感谢编写一个编译器测试用例!
#include <array>
#include <type_traits>
enum class NodeType {
Type1,
Type2,
Type3
};
struct Caps {
NodeType type;
int minValue;
int maxValue;
};
class Node
{
public:
static constexpr std::array<Caps,0> inCaps() { return {}; }
static constexpr std::array<Caps,0> outCaps() { return {}; }
template <class InCaps, class OutCaps>
static constexpr bool intersect(const InCaps &in, const OutCaps &out);
template <class N1, class N2>
static std::enable_if_t<
std::is_base_of_v<Node, N1> &&
std::is_base_of_v<Node, N2> &&
intersect(N1::outCaps(), N2::inCaps()), void>
link2(N1 &prev, N2 &next) {
prev.m_next = &next;
}
protected:
Node* m_next = nullptr;
};
template <typename ThisNode>
class Link
{
public:
template <class N2> void link(N2 &next) {
Node::link2(*static_cast<ThisNode*>(this), next);
}
};
template <class InCaps, class OutCaps>
constexpr bool Node::intersect(const InCaps &in, const OutCaps &out)
{
for (auto i : in) {
for (auto o : out) {
if (i.type == o.type) {
auto minValue = std::max(i.minValue, o.minValue);
auto maxValue = std::min(i.maxValue, o.maxValue);
if (maxValue >= minValue) {
return true;
}
}
}
}
return false;
}
class DerivedNode1 : public Node, public Link<DerivedNode1>
{
public:
static constexpr std::array<Caps,2> outCaps() {
return {{ { NodeType::Type1, 1, 10 },
{ NodeType::Type2, 5, 20 } }};
}
};
class DerivedNode2 : public Node, public Link<DerivedNode2>
{
public:
static constexpr std::array<Caps,1> inCaps() {
return {{ { NodeType::Type1, 8, 12 } }};
}
};
class DerivedNode3 : public Node, public Link<DerivedNode3>
{
public:
static constexpr std::array<Caps,1> inCaps() {
return {{ { NodeType::Type2, 1, 4 } }};
}
};
class DerivedNode4 : public Node, public Link<DerivedNode4>
{
public:
static constexpr std::array<Caps,1> inCaps() {
return {{ { NodeType::Type3, 1, 99 } }};
}
};
int main()
{
DerivedNode1 node1;
DerivedNode2 node2;
DerivedNode3 node3;
DerivedNode4 node4;
Node::link2(node1, node2); // compiles
node1.link(node2);
#if 0
Node::link2(node1, node3); // fails to compile
node1.link(node3);
Node::link2(node1, node4); // fails to compile
node1.link(node3);
#endif
}
C++ 没有正确的交叉点类型。 但是你可以用编译时模板逻辑来伪造它。
假设我将一些功能定义为 mixins:
struct cap_a {
void foo();
};
struct cap_b {
void bar();
};
struct cap_c {
void baz();
};
像这样的容器继承了所有功能:
template<typename... TCaps>
struct intersectable : public TCaps... {};
我可以使用 SFINAE 和可变参数模板创建两个intersectable
的交集。 结果类型仅具有两个输入都具有的功能。
进行交叉连接的第一步是检查一个类型是否在列表中。 这是一个非常基本的可变参数宏。 每次迭代都会弹出一个类型,检查它是否是目标类型,或者是否是下一次迭代。
template<typename TCap, typename... TCaps>
struct has_capability_impl;
template<typename TCap, typename TCapFirst, typename... TCaps>
struct has_capability_impl<TCap, TCapFirst, TCaps...> {
static constexpr bool value = std::is_convertible<TCap, TCapFirst>::value || has_capability_impl<TCap, TCaps...>::value;
};
template<typename TCap>
struct has_capability_impl<TCap> {
static constexpr bool value = false;
};
template<typename TCap, typename... TCaps>
constexpr bool has_capability = has_capability_impl<TCap, TCaps...>::value;
您可以使用 C++17 中的折叠表达式来执行此操作,但这适用于旧版本。
接下来是路口。 这是一个具有 3 种类型的模板:空结果可迭代、左侧和带有 enable_if 的分支。 如果该类型存在于右侧的相交表中,则将该类型移至继承的基类型中的结果。 否则,不要。
对于每次迭代,从 a 中弹出一个类型
template<typename TLRhs, typename TLhs, typename TRhs, typename = void>
struct intersect_impl;
template<typename... TLRCaps, typename TFirst, typename... TLCaps, typename... TRCaps>
struct intersect_impl<intersectable<TLRCaps...>, intersectable<TFirst, TLCaps...>, intersectable<TRCaps...>, std::enable_if_t<has_capability<TFirst, TRCaps...>>> : intersect_impl<intersectable<TLRCaps..., TFirst>, intersectable<TLCaps...>, intersectable<TRCaps...>> { };
template<typename... TLRCaps, typename TFirst, typename... TLCaps, typename... TRCaps>
struct intersect_impl<intersectable<TLRCaps...>, intersectable<TFirst, TLCaps...>, intersectable<TRCaps...>, std::enable_if_t<!has_capability<TFirst, TRCaps...>>> : intersect_impl<intersectable<TLRCaps...>, intersectable<TLCaps...>, intersectable<TRCaps...>> { };
当左侧没有类型时,结果只有双方都存在能力:
template<typename... TLRCaps, typename... TRCaps>
struct intersect_impl<intersectable<TLRCaps...>, intersectable<>, intersectable<TRCaps...>> {
using type = intersectable<TLRCaps...>;
};
template<typename TLhs, typename TRhs>
using intersection = typename intersect_impl<intersectable<>, TLhs, TRhs, void>::type;
Package 在一个简单的 function 中组合实例:
template<typename TLhs, typename TRhs>
intersection<TLhs, TRhs> intersect(TLhs lhs, TRhs rhs) {
return intersection<TLhs, TRhs>{}; // runtime link logic goes here
}
...结果是类型安全的能力交集:
int main() {
intersectable<cap_a, cap_c> ac;
ac.foo();
ac.baz();
intersectable<cap_a, cap_b> ab;
ab.foo();
ab.bar();
auto a = intersect(ac, ab);
a.foo();
a.bar(); // error
a.baz(); // error
}
演示: https://godbolt.org/z/-kd2Qj
同样,这实际上并没有做任何事情,它只是与类型相交。 但是你可以使用这样的东西来使你的链表逻辑类型安全。
无论如何,要添加范围检查,只需将范围的编译时属性工作到intersectable
的定义中
#include <cstdint>
#include <functional>
#include <type_traits>
#include <unordered_set>
struct cap_a {
void foo();
};
struct cap_b {
void bar();
};
struct cap_c {
void baz();
};
template<int Min, int Max, typename... TCaps>
struct intersectable : public TCaps... {
};
template<typename TCap, typename... TCaps>
struct has_capability_impl;
template<typename TCap, typename TCapFirst, typename... TCaps>
struct has_capability_impl<TCap, TCapFirst, TCaps...> {
static constexpr bool value = std::is_convertible<TCap, TCapFirst>::value || has_capability_impl<TCap, TCaps...>::value;
};
template<typename TCap>
struct has_capability_impl<TCap> {
static constexpr bool value = false;
};
template<typename TCap, typename... TCaps>
constexpr bool has_capability = has_capability_impl<TCap, TCaps...>::value;
template<typename TLRhs, typename TLhs, typename TRhs, typename = void>
struct intersect_impl;
template<int LRMin, int LRMax, int LMin, int LMax, int RMin, int RMax, typename... TLRCaps, typename TFirst, typename... TLCaps, typename... TRCaps>
struct intersect_impl<
intersectable<LRMin, LRMax, TLRCaps...>,
intersectable<LMin, LMax, TFirst, TLCaps...>,
intersectable<RMin, RMax, TRCaps...>,
std::enable_if_t<(has_capability<TFirst, TRCaps...>)>>
: intersect_impl<
intersectable<LRMin, LRMax, TLRCaps..., TFirst>,
intersectable<LMin, LMax, TLCaps...>,
intersectable<RMin, RMax, TRCaps...>> { };
template<int LRMin, int LRMax, int LMin, int LMax, int RMin, int RMax, typename... TLRCaps, typename TFirst, typename... TLCaps, typename... TRCaps>
struct intersect_impl<
intersectable<LRMin, LRMax, TLRCaps...>,
intersectable<LMin, LMax, TFirst, TLCaps...>,
intersectable<RMin, RMax, TRCaps...>,
std::enable_if_t<(!has_capability<TFirst, TRCaps...>)>>
: intersect_impl<
intersectable<LRMin, LRMax, TLRCaps...>,
intersectable<LMin, LMax, TLCaps...>,
intersectable<RMin, RMax, TRCaps...>> { };
template<int LMin, int LMax, int RMin, int RMax, typename TResult, typename... TRCaps>
struct intersect_impl<TResult, intersectable<LMin, LMax>, intersectable<RMin, RMax, TRCaps...>> {
using type = TResult;
};
template <typename T>
struct props;
template<int Min, int Max, typename... Caps>
struct props<intersectable<Min, Max, Caps...>> {
static constexpr int min = Min;
static constexpr int max = Max;
};
template<typename TLhs, typename TRhs>
using intersection = typename intersect_impl<
intersectable<(std::max(props<TLhs>::min, props<TRhs>::min)), (std::min(props<TLhs>::max, props<TRhs>::max))>,
TLhs,
TRhs>::type;
template<typename TLhs, typename TRhs>
intersection<TLhs, TRhs> intersect(TLhs lhs, TRhs rhs) {
static_assert((props<TLhs>::max >= props<TRhs>::min && props<TLhs>::min <= props<TRhs>::max) ||
(props<TRhs>::max >= props<TLhs>::min && props<TRhs>::min <= props<TLhs>::max), "out of range");
return intersection<TLhs, TRhs>{}; // runtime intersection logic?
}
int main() {
intersectable<1, 5, cap_a, cap_c> ac;
ac.foo();
ac.baz();
intersectable<3, 8, cap_a, cap_b> ab;
ab.foo();
ab.bar();
auto a = intersect(ac, ab); // result is a intersectable<3, 5, cap_a>
a.foo();
a.bar(); // error
a.baz(); // error
intersectable<10, 15, cap_a, cap_b> ab_out_of_range;
auto a0 = intersect(ac, ab_out_of_range);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.