【问题标题】:How to reverse template parameters when tag dispatching is used?使用标签调度时如何反转模板参数?
【发布时间】:2014-02-09 20:04:55
【问题描述】:

我一直在使用tag dispatching 来模拟问题。

注意:这段代码运行,我只是对不涉及太多编码的解决方案感兴趣,只是为了反转调度算法的参数。

这里是有问题的代码:

#include <iostream>


struct A_tag {}; 
struct B_tag {};

// Tag getter
template<typename Type>
struct get { typedef void tag; }; 

namespace dispatch { 

    template<typename T1, typename T2>
    struct my_algorithm {};

    template<>
    struct my_algorithm<A_tag, B_tag>
    {
        template<typename P1, typename P2> 
        static void apply(P1 const& p1, P2 const& p2) 
        { 
            auto p1val = p1.value(); 
            auto p2val = p2.someFunction();

            std::cout << p1val << " " << p2val << std::endl;
        }  
    };

     //Specialization reversal: can this be made shorter?  
     //A lot of lines used just to reverse the algorithm. 
    template<>
    struct my_algorithm<B_tag, A_tag>
    {
        template<typename P2, typename P1>
        static void apply(P2 const & p2, P1 const & p1)
        {
            my_algorithm<typename get<P1>::tag, typename get<P2>::tag>::apply(p1, p2); 
        }
    };
}

// First and Second are test classes. 
class First
{
    public: 
        double value() const { return 5; };
};

template<>
struct get<First>
{
    typedef A_tag tag; // Expect First to behave as A
};

class Second
{
    public: 
        double someFunction() const { return 6; };
};

template<>
struct get<Second>
{
    typedef B_tag tag; // Expect Second behave as B
};

// Tag dispatcher. 
template<typename P1, typename P2>
void my_algorithm(P1 const & p1, P2 const & p2)
{
    dispatch::my_algorithm<typename get<P1>::tag, typename get<P2>::tag>::apply(p1, p2); 
}

int main(int argc, const char *argv[])
{
    First f; 
    Second s; 

    my_algorithm(f, s);  

    // Commutative algorithm.  
    my_algorithm(s,f); 

    return 0;
}

无论模板参数的顺序如何,一些分派算法的工作方式都是一样的。 dispatch::my_algorithm::apply 函数完成了本示例中的所有工作。我已经设法使用 dispatch::my_algorithm 类的完整模板专业化来反转模板参数,并使用反转参数调用静态 apply 函数。

可以更快地进行参数反转吗?即使我设法打包并“调用它”,当apply 接受更多参数时,其他算法会发生什么?

【问题讨论】:

    标签: c++ templates template-specialization


    【解决方案1】:

    我个人会考虑使用重载,而不是使用这么多的类。而且,不知何故,您还需要一个标签选择器。

    让我们从标签选择器开始。

    // Note: to start simply we assume that a tag is only ever present once
    //       if not, we just need to add an index.
    template <typename Tag,
              typename Type,
              typename std::disable_if<same_type<Tag, typename get<Head>::tag>::type>
    struct has_tag: std::false_type {};
    
    template <typename Tag,
              typename Type,
              typename std::enable_if<same_type<Tag, typename get<Head>::tag>::type>
    struct has_tag: std::true_type {};
    
    template <typename Tag, typename Head, typename... Tail>
    struct tag_selector {
        using type = if_<has_tag<Tag, Head>,
                         Head,
                         typename tag_selector<Tag, Tail...>::type>;
    }; // tag_selector
    
    template <typename Tag>
    struct tag_selector {}; // compile-time error if tag absent
    
    // A helper function
    template <typename Result, typename Head, typename... Tail>
    auto tag_select_impl(std::true_type, Head const& head, Tail const&... tail)
        -> Result const&
    {
        return Head;
    }
    
    template <typename Result, typename Head, typename NH, typename... Tail>
    auto tag_select_impl(std::false_type, Head const&, NH const& nh, Tail const&... tail)
        -> Result const&
    {
        return tag_select_impl<Result>(has_tag<Tag, NH>{}, nh, tail);
    }
    
    // And now the runtime function
    template <typename Tag, typename Head, typename... Tail>
    auto tag_select(Tag, Head const& head, Tail const&... tail) ->
        typename tag_selector<Tag, Head, Tail...>::type const&
    {
        using Result = typename tag_selector<Tag, Head, Tail...>::type;
        return tag_select_impl<Result>(has_tag<Tag, Head>{}, head, tail);
    }
    

    所以,简而言之,所有这些都只是,因此tag_select(tag, args...) 返回与tag 匹配的第一个参数。好消息是它非常通用:)

    那么,我们就可以实际实现算法了:

    void internal_impl(First const& first, Second const& second) {
         std::cout << first.value() << " " << second.someFunction() << "\n";
    } // internal_impl
    
    template <typename A, typename B>
    void interface(A const& a, B const& b) {
        internal_impl(tag_select(A_tag{}, a, b), tag_select(B_tag{}, a, b));
    }
    

    注意:标签选择是 1. murky(可能更干净)和 2. 现在只允许 const&amp; 参数,这很烦人;可能有办法解决这两个问题。

    【讨论】:

    • +1 :) 这很酷。我想知道是否可以使用可变参数模板来反转 N 个参数,但我对它们了解不多,所以我在了解更多信息后将其放在我的待办事项中。我读过 structs + static apply 可能比用于调度算法的函数模板更好,因为可以使用继承和封装(需要状态的算法等)。
    • 警告:如果你使用两次相同的标签,你将有两次相同的参数(如果get&lt;A&gt;::tag == get&lt;B&gt;::tag)。
    • @Jarod42:是的,这就是has_tag 上面的评论的内容。此外,它不可能真的是两次采用相同类型的方法(但必须打乱参数顺序)。
    • @MatthieuM.:我认为理想的解决方案是构建一个索引类型排序的index_sequence(我的意思是从B, A, A, C -> 1/*A*/, 2/*A*/, 0/*B*/, 3/*C*/),所以我们可以按索引输入类型.
    • @Jarod42:不幸的是,问题是如何获取序列;应该是1, 2, 0, 3 还是2, 1, 0, 3
    【解决方案2】:

    带标签修改有顺序:

    struct A_tag { static const int value = 0; };
    struct B_tag { static const int value = 1; };
    

    然后用:

    template <typename P1, typename P2>
    struct ordered_type
    {
    private:
        typedef typename get<P1>::tag tag_P1;
        typedef typename get<P2>::tag tag_P2;
        static const bool ordered = tag_P1::value <= tag_P2::value;
    public:
    
        typedef typename std::conditional<ordered, tag_P1, tag_P2>::type tag1;
        typedef typename std::conditional<ordered, tag_P2, tag_P1>::type tag2;
        typedef typename std::conditional<ordered, P1, P2>::type type1;
        typedef typename std::conditional<ordered, P2, P1>::type type2;
    
        static constexpr const type1& getFirst(const P1& p1, const P2& p2)
        {
            return std::get<ordered ? 0 : 1>(std::tie(p1, p2));
        }
        static constexpr const type2& getSecond(const P1& p1, const P2& p2)
        {
            return std::get<ordered ? 1 : 0>(std::tie(p1, p2));
        }
    };
    

    您可以删除您的专业化逆转并编写您的标签调度程序:

    // Tag dispatcher.
    template<typename P1, typename P2>
    void my_algorithm(P1 const & p1, P2 const & p2)
    {
        typedef ordered_type<P1, P2> h;
    
        dispatch::my_algorithm<typename h::tag1,
                               typename h::tag2>::apply(h::getFirst(p1, p2),
                                                        h::getSecond(p1, p2));
    }
    

    【讨论】:

    • 谢谢,为标签值+1,完全忘记了这一点。当标签调度程序解析了 3 个参数时会发生什么?
    • @tomislav-maric:如果您有 C_tag::value = 2 并且仍有 2 种类型要排序,则此解决方案无需修改即可工作。如果您希望您的算法采用 3+ 个参数,这是可行的,但需要更多代码来对类型进行排序...
    猜你喜欢
    • 1970-01-01
    • 2014-06-12
    • 2017-07-28
    • 2010-12-08
    • 2012-05-30
    • 1970-01-01
    • 1970-01-01
    • 2022-11-25
    • 2013-04-01
    相关资源
    最近更新 更多