【问题标题】:Variadic variadic template templates, again可变参数模板模板,再次
【发布时间】:2013-09-17 14:02:32
【问题描述】:

我见过例如same issue 上的一个相关问题,但我有一个不同的问题,我认为无法以任何其他方式解决。

这是函数 any,它计算 一元谓词 F 对于模板参数包表示(类型列表)P 中的任何元素是否为真:

template <template <typename...> class F, typename P>
struct any;

template <
    template <typename...> class F,
    template <typename...> class C, typename E, typename... En
>
struct any <F, C <E, En...> > :
    public _if <F <E>{}, _true, any <F, C <En...> > > { };

template <template <typename...> class F, template <typename...> class C>
struct any <F, C <> > : public _false { };

我的_true/_false 相当于std::integral_constant &lt;bool, true/false&gt;_if &lt;C, T, E&gt; 相当于typename std::conditional &lt;C, T, E&gt;::type(细节与问题无关,见下文)。

例如,可以写

template <typename...> struct pack { };
template <typename T> using is_int = eq <int, T>;

any <is_int, pack <int, void, float, double> >();   // evaluates to true
any <is_int, pack <char, void, float, double> >();  // evaluates to false

其中eq 等价于std::is_same

二元谓词的扩展如下:

template <template <typename...> class F, typename P, typename Q>
struct any2;

template <
    template <typename...> class F,
    template <typename...> class C, typename E, typename... En,
    template <typename...> class D, typename H, typename... Hn
>
struct any2 <F, C <E, En...>, D <H, Hn...> > :
    public _if <F <E, H>{}, _true, any2 <F, C <En...>, D <Hn...> > > { };

template <
    template <typename...> class F,
    template <typename...> class C, typename Q
>
struct any2 <F, C <>, Q> : public _false { };

我们现在可以写的地方

typedef pack <int, void, float, double> A;
typedef pack <void, float, double, int> B;
typedef pack <void, float, double, double> C;

any2 <eq, A, B>();  // false
any2 <eq, A, C>();  // true

问题来了。我们能否将这种方法扩展到 n-ary 谓词,对n 输入“packs”进行操作?

此问题与the previous one 不同,每个输入包的一个元素同时需要用于评估F &lt;...&gt;

这是一个虚构的尝试:

template <template <typename...> class F, typename... P>
struct any_n;

template <
    template <typename...> class F,
    template <typename...> class... C, typename... E, typename... En
>
struct any_n <F, C <E, En...>...> :
    public _if <F <E...>{}, _true, any_n <F, C <En...>...> > { };

template <
    template <typename...> class F,
    template <typename...> class C, typename... P
>
struct any_n <F, C <>, P...> : public _false { };

当然不能编译。那么,可以写C &lt;E, En...&gt;... 之类的东西吗?那么C, E, En 的类型是什么?

我怀疑答案是否定的。

这样的语法会非常方便,例如在方案宏中。过去,我编写了此语法的 C++ 模板实现,最多支持两个级别,使用符号 dotsetc 表示 ...。但是得到编译器的支持就完全不一样了(尤其是如果你需要在同一天编译这个东西)。

【问题讨论】:

    标签: c++ c++11 variadic-templates


    【解决方案1】:

    Clang 3.3 和 GCC 4.8.1 都接受您对 any_n&lt;&gt; 的定义而没有任何警告,这让我相信它是完全有效的,除了它是逻辑的事实。


    为什么不起作用?

    在定义any_n&lt;&gt; 的方式中,它要求每个包的第一个元素以外的所有元素完全相同,因为在struct any_n &lt;F, C &lt;E, En...&gt;...&gt; 中,En... 必须为每个参数包重复 C&lt;&gt; 展开。

    也就是说,考虑

    template<typename...>
    struct Pack;
    
    typedef Pack<int, float, double, bool> A;
    typedef Pack<void, float, double, bool> B;
    typedef Pack<float, float, double, bool> C;
    

    template<typename...>
    struct SomeMetaFunction
    {...};
    

    实例化any_n&lt;SomeMetaFunction, A, B, C&gt;成功绑定any_n&lt;&gt;的模板参数如下:

    F => SomeMetaFunction
    C => std::tuple
    E... => [int, void, float]
    F... => [float, double, bool]
    

    根据其第二个专业化。

    例如尝试实例化any_n&lt;SomeMetaFunction, A, B, C, D&gt;,其中 D 定义为

    typedef std::tuple <char, int, double, bool> D;
    

    如预期的那样导致undefined any_n&lt;&gt; 错误。


    怎么做

    您只需对any_n&lt;&gt; 的定义稍作修改即可实现您想要的。这个想法是使用递归列表的概念,就像函数式语言一样。

    首先,您需要一个元函数将pack&lt;a, b, c, ..., z&gt; 转换为pack&lt;a, pack&lt;b, c, ... z&gt;&gt;

    template<typename...>
    struct toRecursiveList;
    
    //a recursive list already
    template<template <typename...> class Pack, typename H, typename... T>
    struct toRecursiveList<Pack<H, Pack<T...>>>
    {
        using type = Pack<H, Pack<T...>>;
    };
    
    //several elements
    template<template <typename...> class Pack, typename H, typename... T>
    struct toRecursiveList<Pack<H, T...>>
    {
        using type = Pack<H, Pack<T...>>;
    };
    
    //one element
    template<template <typename...> class Pack, typename H>
    struct toRecursiveList<Pack<H> >
    {
        using type = Pack<H, Pack<>>;
    };
    
    //empty
    template<template <typename...> class Pack>
    struct toRecursiveList<Pack<>>
    {
        using type = Pack<>;
    };
    
    //missing pack
    template<typename H, typename... T>
    struct toRecursiveList<H, T...>
    {
        template<typename...>
        struct Pack;
        using type = Pack<H, Pack<T...>>;
    };
    

    any_n&lt;&gt; 的基本情况确保参数在必要时在第一步转换为递归列表。

    template <template <typename...> class F, typename... P>
    struct any_n :
            public any_n<F, typename toRecursiveList<P>::type...>
    {};
    

    现在我们保证每个包最多有两个元素,因为如果没有,基本情况会将其转换为这种表示形式。利用这个事实,主要的递归变成了以下内容。

    template <
        template <typename...> class F,
        template <typename...> class... C, typename... E, typename... En
    >
    struct any_n <F, C <E, En>...> : //two elements each, no need for double pack expansion on En
        public _if <F <E...>{}, _true, any_n <F, typename toRecursiveList<En>::type...>>::type { }; 
        //                                                ^ ensures a recursive list is passed onto the next recursion step
    

    递归保护保持不变。

    template <
        template <typename...> class F,
        template <typename...> class C, typename... P
    >
    struct any_n <F, C <>, P...> : public _false { };
    

    前面的示例 any_n&lt;SomeMetaFunction, A, B, C, D&gt; 现在可以按预期编译。

    如需直播SSCCE 示例,请点击here

    【讨论】:

    • 棘手!您有 SSCCE / 现场示例吗?
    • @brunocodutra 刚刚浏览,我看到了这个答案,我不记得当时看到过!我更喜欢使用 boost。我的问题是语言是否支持这一点,很明显它不支持,你必须自己编程。
    • @brunocodutra 同时,我发现了另一个解决方案,not 必须转换为旧式递归列表。粗略地说,我定义了元函数carcdr(lisp 中的头/尾等效项),然后推广到carscdrs,它们可以并行处理多个列表:cars 将一个包作为输入列表(包)并返回包含每个列表的第一个元素(头)的包;同样,cdrs 返回一个包含每个列表尾部的包。这有点类似于 boost 的 zip 视图。将它作为另一个答案发布需要一些工作,所以我在这里总结一下。
    【解决方案2】:

    我会使用boost::mpl::zip_view 一次遍历您的所有列表。实际上,我认为链接中的示例正是您所需要的,除了您想要find_if 而不是transform_view

        #include <boost/mpl/zip_view.hpp>
        #include <boost/mpl/find_if.hpp>
        #include <boost/mpl/placeholders.hpp>
        #include <boost/mpl/unpack_args.hpp>
        #include <boost/mpl/vector.hpp>
        #include <boost/mpl/range_c.hpp>
        #include <boost/mpl/or.hpp>
        #include <boost/mpl/vector_c.hpp>
        #include <boost/mpl/equal_to.hpp>
        #include <boost/mpl/not_equal_to.hpp>
        #include <boost/mpl/not.hpp>
        #include <boost/mpl/and.hpp>
        #include <iostream>
    
        using namespace boost::mpl;
        using namespace boost::mpl::placeholders;
    
        template <class Predicate, class ... Sequences>
        struct any_n
        {
            typedef zip_view<vector<Sequences...> > seq;
            typedef typename not_<
                typename boost::is_same<
                    typename find_if<
                        seq,
                        unpack_args<Predicate> >::type,
                    typename end<seq>::type>::type>::type type;
        };
    
        typedef not_equal_to<boost::is_same<_1, _2>,
                             boost::is_same<_2, _3> >pred1;
    
        typedef or_<equal_to<_1, _2>,
                    equal_to<_2, _3> > pred2;
    
        typedef any_n<pred1,
                      range_c<int,0,10>,
                      vector_c<unsigned, 1, 4, 2, 5>,
                      vector_c<short, 0, 0, 2, 7, 4> >::type found1;
    
        typedef any_n<pred2,
                      range_c<int,0,10>,
                      vector_c<unsigned, 1, 4, 2, 5>,
                      vector_c<short, 0, 0, 2, 7, 4> >::type found2;
    
        int main()
        {
            std::cout << std::boolalpha << found1() << ' ' << found2() << std::endl;
        }
    

    如果您不熟悉 boost 的模板元编程方法(即Boost.TemplateMetaprogrammingLibrary),那么这似乎有点让人不知所措(甚至非常令人不知所措)。我愿意购买的关于编程的唯一一本书(至少到目前为止)是《C++ 模板元编程》,如果您对这些内容感兴趣,我强烈推荐它。
    我发现this 问题指向这本书。

    【讨论】:

    • 谢谢。这确实很好,尽管我对语言支持的内容很感兴趣。我过去开发了自己的元编程库,它看起来真的很像语言 ML(并且也可以处理这个问题)。一行代码能表达的东西令人印象深刻,但如果你被冲昏了头脑,你会等待几分钟编译...
    • @iavr 虽然该语言不直接支持 双包扩展,但您肯定可以解决它,就像我试图在我的答案中展示的那样。正如您所说,要解决此类元编程问题,关键是要像使用函数式语言(例如 [S]ML)时那样思考。
    猜你喜欢
    • 2016-12-01
    • 2012-03-28
    • 1970-01-01
    • 1970-01-01
    • 2013-09-14
    • 1970-01-01
    • 2020-04-29
    • 2014-09-08
    • 2014-04-12
    相关资源
    最近更新 更多