繁体   English   中英

C++ 中带有编译时检查的链表

[英]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);
}

演示: https://godbolt.org/z/zz9-Vg

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM