首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用C++17变量模板在编译时遍历树

使用C++17变量模板在编译时遍历树
EN

Stack Overflow用户
提问于 2021-06-24 12:26:33
回答 2查看 614关注 0票数 10

我目前正在研究如何使用C++ (C++17)可变模板来生成高效的、实时的电路模拟。

我的目标是利用各种模板来定义可以在编译时遍历的树。要定义这样的树,我使用以下三个结构:

代码语言:javascript
复制
template <auto Tag> struct Leaf
{
    static constexpr auto tag = Tag;
};

template <typename ... Children> struct Branch
{
    static constexpr auto child_count = sizeof ... (Children);
    
    template <typename Lambda> constexpr void for_each_child(Lambda && lambda)
    {
        // TODO: Execute 'lambda' on each child.
    }
    
    std::tuple<Children ...> m_children {};
};

template <typename Root> struct Tree
{
    template <auto Tag> constexpr auto & get_leaf()
    {
        // TODO: Traverse the tree and find the leaf with tag 'Tag'.
        
        // If there's no leaf with tag 'Tag' the program shouldn't compile.
    }
    
    Root root {};
};

使用上述树的定义,我们可以定义一组电路组件如下:

代码语言:javascript
复制
template <auto Tag> struct Resistor : Leaf<Tag>
{
    float resistance() { return m_resistance; }
    
    float m_resistance {};
};

template <auto Tag> struct Capacitor : Leaf<Tag>
{
    float resistance() { return 0.0f; }
    
    float m_capacitance {};
};

template <typename ... Children> struct Series : Branch<Children ...>
{
    using Branch<Children ...>::for_each_child;
    
    float resistance()
    {
        float acc = 0.0f;
        
        for_each_child([&acc](auto child) { acc += child.resistance(); });
        
        return acc;
    }
};

template <typename ... Children> struct Parallel : Branch<Children ...>
{
    using Branch<Children ...>::for_each_child;
    
    float resistance()
    {
        float acc = 0.0f;
        
        for_each_child([&acc](auto child) { acc += 1.0f / child.resistance(); });
        
        return 1.0f / acc;
    }
};

接下来,使用上面的组件,我们可以表示这样一个特定的电路:

代码语言:javascript
复制
enum { R0, R1, C0, C1 };

using Circuit =
    Tree<
        Parallel<
            Series<
                Resistor<R0>,
                Capacitor<C0>
            >, // Series
            Series<
                Resistor<R0>,
                Capacitor<C1>
            > // Series
        > // Parallel
    >; // Tree

...where R0、R1、C0和C1是我们在编译时用于访问组件的标记。例如,一个非常基本的用例可以是:

代码语言:javascript
复制
int main()
{
    Circuit circuit {};
    
    circuit.get_leaf<R0>().m_resistance  =  5.0E+3f;
    circuit.get_leaf<C0>().m_capacitance = 10.0E-3f;
    circuit.get_leaf<R1>().m_resistance  =  5.0E+6f;
    circuit.get_leaf<C1>().m_capacitance = 10.0E-6f;
    
    std::cout << circuit.root.resistance() << std::endl;
}

我无法理解的是如何实现for_each_child和get_leaf函数。我尝试过使用if-constexpr语句和模板-structs的不同方法,但没有找到一个好的解决方案。多样化的模板是有趣的,但同时令人生畏。任何帮助都将不胜感激。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2021-07-04 22:13:26

在研究了各种关于C++变量模板的文章之后,我设法修补了这个问题的解决方案。

首先,为了实现for_each_child,我们使用以下助手函数,它作为一个for-循环,在编译时未滚动:

代码语言:javascript
复制
template <auto from, auto to, typename Lambda>
    static inline constexpr void for_constexpr(Lambda && lambda)
{
    if constexpr (from < to)
    {
        constexpr auto i = std::integral_constant<decltype(from), from>();
        
        lambda(i);
        
        for_constexpr<from + 1, to>(lambda);
    }
}

通过使用这个助手函数,我们可以实现如下for_each_child:

代码语言:javascript
复制
template <typename ... Children> struct Branch
{
    static constexpr auto children_count = sizeof ... (Children);

    template <typename Lambda> constexpr void for_each_child(Lambda && lambda)
    {
        for_constexpr<0, children_count>([lambda, this](auto i)
        {
            lambda(std::get<i>(m_children));
        });
    }
    
    std::tuple<Children ...> m_children {};
};

接下来,为了实现get_leaf,我们使用了一系列不同的辅助函数。作为卡列斯 建议,我们可以将问题分为两个步骤。首先,我们计算出从根到想要的叶子的路径;然后,我们可以按照这条路径从树中提取叶子。

路径可以表示为如下索引序列:

代码语言:javascript
复制
template <auto ...indices> using Path = std::index_sequence<indices...>;

我们需要的第一个帮助函数检查节点是否有带有给定标记的叶。

代码语言:javascript
复制
template <auto tag, class Node> struct has_path
{
    static constexpr
        std::true_type
            match(const Leaf<tag>);
    
    template <class ...Children> static constexpr
        typename std::enable_if<
            (has_path<tag, Children>::type::value || ...),
            std::true_type
        >::type
            match(const Branch<Children...>);
    
    static constexpr
        std::false_type
            match(...);
    
    using type = decltype(match(std::declval<Node>()));
};

我们只需在节点上进行模式匹配。如果是叶子,我们必须确保它有正确的标签。如果它是一根树枝,我们需要确保其中一个孩子有一个带有标签的叶子。

下一个助手函数要复杂一些:

代码语言:javascript
复制
template <auto tag, class Node, auto ...indices> struct find_path
{
    template <auto index, class Child, class ...Children> struct search_children
    {
        static constexpr auto fold()
        {
            if constexpr(has_path<tag, Child>::type::value)
            {
                return typename find_path<tag, Child, indices..., index>::type();
            }
            else
            {
                return typename search_children<index + 1, Children...>::type();
            }
        }
        
        using type = decltype(fold());
    };
    
    static constexpr
        Path<indices...>
            match(const Leaf<tag>);
    
    template <class ...Children> static constexpr
        typename search_children<0, Children...>::type
            match(const Branch<Children...>);
    
    using type = decltype(match(std::declval<Node>()));
};

我们在indices模板参数中积累路径。如果我们正在调查的节点(通过模板参数Node)是叶,则检查它是否有正确的标记,如果有,返回累积路径。如果节点是一个分支,我们必须使用助手函数search_children,它遍历分支中的所有子节点。对于每个孩子,我们首先检查这个孩子是否有一个带有给定标签的叶子。如果是这样的话,我们将当前索引(由模板参数index提供)附加到累积路径中,并递归地调用该子路径上的find_path。如果子代没有带有给定标记的叶子,我们将尝试下一个子节点,以此类推。

最后,我们定义了一个帮助函数,它可以提取给定路径的叶子:

代码语言:javascript
复制
template <class Node>
    static inline constexpr auto &
        get(Node & leaf, Path<> path)
{
    return leaf;
}

template <auto index, auto ...indices, class Node>
    static inline constexpr auto &
        get(Node & branch, Path<index, indices...> path)
{
    auto & child = std::get<index>(branch.m_children);
    
    return get(child, Path<indices...>());
}

使用find_pathget,我们可以按以下方式实现get_leaf

代码语言:javascript
复制
template <typename Root> struct Tree
{
    template <auto tag> constexpr auto & get_leaf()
    {
        constexpr auto path = typename implementation::find_path<tag, Root>::type {};
        
        return implementation::get(root, path);
    }
    
    Root root;
};

这里有一个指向godbolt.org的链接,它演示了代码按照Clang预期的方式编译和工作:

/.

票数 3
EN

Stack Overflow用户

发布于 2021-06-24 14:28:06

for_each_childstd::index_sequence相当简单。

代码语言:javascript
复制
template <typename ... Children> struct Branch
{
    using indexes = std::index_sequence_for<Children...>;
    static constexpr auto child_count = sizeof... (Children);
    
    template <typename Lambda> constexpr void for_each_child(Lambda && lambda)
    {
        for_each_child_impl(std::forward<Lambda>(lambda), indexes{});
    }
    
    std::tuple<Children ...> m_children {};

private:
    template <typename Lambda, std::size_t... Is> constexpr void for_each_child_impl(Lambda && lambda, std::index_sequence<Is...>)
    {
        (lambda(std::get<Is>(m_children)), ...);
    }
};

get_leaf则稍显棘手。首先,我们计算出通向所需叶子的路径是什么,然后我们遵循来自root的路径。

代码语言:javascript
复制
template <std::size_t I, typename>
struct index_sequence_cat;

template <std::size_t I, std::size_t... Is>
struct index_sequence_cat<I, std::index_sequence<Is...>> {
    using type = std::index_sequence<I, Is...>;
};

template <std::size_t I, typename Ix>
using index_sequence_cat_t = typename index_sequence_cat<I, Ix>::type;

template<typename, auto Tag, typename, std::size_t... Is> 
struct leaf_index {};

template<auto Tag, typename T, std::size_t... Is> 
using leaf_index_i = typename leaf_index<void, Tag, T, Is...>::index;

template<auto Tag, std::size_t I> 
struct leaf_index<void, Tag, Leaf<Tag>, I> {
    using index = std::index_sequence<I>;
};

template<typename, auto, std::size_t, typename...>
struct branch_index {};

template<auto Tag, std::size_t I, typename... Args>
using branch_index_i = typename branch_index<void, Tag, I, Args...>::index;

template<auto Tag, std::size_t I, typename First, typename... Args>
struct branch_index<std::void_t<leaf_index_i<Tag, First, I>>, Tag, I, First, Args...> {
    using index = leaf_index_i<Tag, First, I>;
};

template<auto Tag, std::size_t I, typename First, typename... Args>
struct branch_index<std::void_t<branch_index_i<Tag, I + 1, Args...>>, Tag, I, First, Args...> {
    using index = branch_index_i<Tag, I + 1, Args...>;
};

template<auto Tag, typename... Children, std::size_t I> 
struct leaf_index<void, Tag, Branch<Children...>, I> {
    using index = index_sequence_cat_t<I, branch_index_i<Tag, 0, Children...>>;
};

template<auto Tag, typename... Children> 
struct leaf_index<std::void_t<branch_index_i<Tag, 0, Children...>>, Tag, Branch<Children...>> {
    using index = branch_index_i<Tag, 0, Children...>;
};

template <typename Root> struct Tree
{
    template <auto Tag> constexpr auto & get_leaf()
    {
        return get_leaf(leaf_index<Tag, root>{});
    }
    
    Root root {};
private:
    template <std::size_t... Is>
    auto & get_leaf(std::index_sequence<Is...>)
    {
        return get_leaf<Is...>(root);
    }

    template<std::size_t I, typename T>
    auto& get_leaf(T & branch)
    {
        return std::get<I>(branch.m_children);
    }
    
    template<std::size_t I, std::size_t J, std::size_t... Is, typename T>
    auto& get_leaf(T & branch)
    {
        return get_leaf<J, Is...>(std::get<I>(branch.m_children));
    }
};
票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68115753

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档