代码之家  ›  专栏  ›  技术社区  ›  Eric Niebler

BOOST\u PP\u SEQ\u FOLD\u LEFT如何工作?

  •  11
  • Eric Niebler  · 技术社区  · 8 年前

    我需要编写一个宏来处理任意长的列表,比如 (A)(B)(C) . 如果我可以接受一个Boost依赖,我只会使用 BOOST_PP_SEQ_ 宏系列。不幸的是,我做不到,所以我只能试着弄清楚它是如何工作的。这东西并不明显。

    这里的任何人都可以编写一个简单、独立的实现,比如, BOOST_PP_SEQ_FOLD_LEFT 让我看看?特别是,我想改变:

    template_(class A, class B, class C)(
        requires IsFoo<A> && IsBar<B>)(
        requires IsBaz<C>)
    void frobozzle(A, B, C);
    

    改写为:

    template<class A, class B, class C,
        int dummy = 0,
        std::enable_if_t<dummy == 0 && (IsFoo<A> && IsBar<B>), int> = 0,
        std::enable_if_t<dummy == 0 && (IsBaz<C>), int> = 0>
    void frobozzle(A, B, C);
    

    可以有任意数量的 requires 子句,它们应该各自有自己的 enable_if_t . 我让它和一个一起工作 要求 子句,但在此过程中我耗尽了C预处理器fu。

    可以假设一个符合std的预处理器,因为我不需要MSVC支持。

    3 回复  |  直到 8 年前
        1
  •  13
  •   Mara Bos    8 年前

    如果在语法中添加一组额外的括号,则可以不限制“required”子句的数量,并且宏相对较少:

    template_((class A, class B, class C)
        (requires IsFoo<A> && IsBar<B>)
        (requires IsBaz<C>)
    )
    void frobozzle(A, B, C);
    

    宏:

    #define template_(...) template_impl_ADD_END(template_impl_LIST __VA_ARGS__) >
    
    #define template_impl_ADD_END(...) template_impl_ADD_END2(__VA_ARGS__)
    #define template_impl_ADD_END2(...) __VA_ARGS__ ## _END
    
    #define template_impl_LIST(...) template<__VA_ARGS__, int dummy = 0 template_impl_LIST_1
    
    #define template_impl_LIST_1(...) , std::enable_if_t<dummy == 0 && template_impl_REQUIRES(__VA_ARGS__), int> = 0 template_impl_LIST_2
    #define template_impl_LIST_2(...) , std::enable_if_t<dummy == 0 && template_impl_REQUIRES(__VA_ARGS__), int> = 0 template_impl_LIST_1
    
    #define template_impl_REQUIRES(...) (template_impl_REQUIRES_ ## __VA_ARGS__)
    #define template_impl_REQUIRES_requires
    
    #define template_impl_LIST_END
    #define template_impl_LIST_1_END
    #define template_impl_LIST_2_END
    

    使用这些宏,上述示例将扩展为:

    template <class A, class B, class C,
              int dummy = 0,
              std::enable_if_t<dummy == 0 && (IsFoo<A> && IsBar<B>), int> = 0,
              std::enable_if_t<dummy == 0 && (IsBaz<C>), int> = 0>
    
    void frobozzle(A, B, C);
    

    解释

    考虑以下宏:

    #define a(x) [x] b
    #define b(x) [x] a
    

    有了这些,这:

    a (1) (2) (3) (4)
    

    将引起如下膨胀的“连锁反应”:

    a (1) (2) (3) (4)
    [1] b (2) (3) (4)
    [1] [2] a (3) (4)
    [1] [2] [3] b (4)
    [1] [2] [3] [4] a
    

    预处理器中不允许递归,但这种连锁反应不是递归,因为宏的调用只在前一个宏的扩展之后发生,而不是在扩展期间发生,因为 ( 不是扩张的一部分。(尽管如此,请参见 https://wg21.link/cwg268 )

    不幸的是,虽然这将很好地循环一系列 (A)(B)(C) ,它将在末尾留下一个额外的标记:所使用的两个宏之一的名称。我用来摆脱这个问题的技巧是用另一个宏调用包装整个列表,该宏调用将附加(使用concat操作符 ## ) _END 完全扩展后,它将成为:

    [1] [2] [3] [4] a_END
    

    然后,我们可以通过定义以下内容简单地去掉最后一个标记:

    #define a_END
    #define b_END
    

    如果我们不能包装整个列表,就无法知道何时到达最后一个元素。唯一发生的事情是 a b 剩下的没有 ( 这就意味着它不会扩展 b 是函数样式宏。(我们不能仅仅定义 b 扩展到零,因为 b 已经是宏,但如果没有 ( .)

    为什么是两个宏?

    当我们试图引发如上所述的连锁反应时,只有一个宏像这样:

    #define a(x) [x] a
    

    这行不通:

    a (1) (2) (3) (4)
    [1] a (2) (3) (4) // Doesn't expand further
    

    这是因为“(无限)递归保护”是如何工作的:如果在宏扩展过程中,生成了一个带有被扩展宏名称的标记,则标记为“不可扩展”,这意味着它永远不会再次扩展。看见 http://eel.is/c++draft/cpp.rescan#2

    这意味着 被标记为“无法扩展”,我们的连锁反应在第一步之后就停止了。我们通过使用两个宏来绕过此规则来避免这种情况: a(..) 不会生成任何具有自己名称的标记,但只能生成具有其他宏名称的标记 b . 扩展 就在那里结束之前 b 因为没有 ( 但是在b之后,因为我们在a的扩展中。扩展完成后,我们不再在a的扩展中 ,重新检查令牌,并正确调用 b 已找到: b(..) . 将生成一个名为 再说一次,但既然我们不再是第一个 ,这一个将不会被标记为“无法扩展”,连锁反应将继续。

        2
  •  2
  •   SirGuy    8 年前

    好吧,我想你可以用一个快速的脏东西:

    #include <iostream>
    
    #define LIST (1)(2)(3)(4)
    
    #define EAT2(list)
    #define EAT(list) EAT2 list
    #define KEEP(x) x EAT2(
    #define STRINGIFY2(x) #x
    #define STRINGIFY(x) STRINGIFY2(x)
    
    #define HEAD(list) KEEP list )
    #define TAIL(list) EAT(list)
    int main()
    {
        std::cout << STRINGIFY(HEAD(LIST)) << std::endl;
        std::cout << STRINGIFY(TAIL(LIST)) << std::endl;
    }
    

    基本上,您需要掌握如何调用宏。
    例如:

    HEAD((1)(2))
    

    扩展到

    KEEP (1)(2) )
    

    扩展到

    1 EAT2 ((2))
    

    扩展到

    1
    

    这不是一个完整的答案,但我认为可以作为你想要做什么的起点。

    编辑

    我现在已经知道如何提高。PP进行迭代,但它并不漂亮,您基本上可以手动写出最大大小的迭代。

    #define CONCAT2(x, y) x##y
    #define CONCAT(x, y) CONCAT2(x, y)
    
    #define SEQ_SIZE(seq) CONCAT(SEQ_SIZE_, SEQ_SIZE_0 seq)
    
    # define SEQ_SIZE_0(_) SEQ_SIZE_1
    # define SEQ_SIZE_1(_) SEQ_SIZE_2
    # define SEQ_SIZE_2(_) SEQ_SIZE_3
    # define SEQ_SIZE_3(_) SEQ_SIZE_4
    # define SEQ_SIZE_4(_) SEQ_SIZE_5
    # define SEQ_SIZE_5(_) SEQ_SIZE_6
    # define SEQ_SIZE_6(_) SEQ_SIZE_7
    # define SEQ_SIZE_7(_) SEQ_SIZE_8
    # define SEQ_SIZE_8(_) SEQ_SIZE_9
    # define SEQ_SIZE_9(_) SEQ_SIZE_10
    # define SEQ_SIZE_10(_) SEQ_SIZE_11
    # define SEQ_SIZE_SEQ_SIZE_0 0
    # define SEQ_SIZE_SEQ_SIZE_1 1
    # define SEQ_SIZE_SEQ_SIZE_2 2
    # define SEQ_SIZE_SEQ_SIZE_3 3
    # define SEQ_SIZE_SEQ_SIZE_4 4
    # define SEQ_SIZE_SEQ_SIZE_5 5
    # define SEQ_SIZE_SEQ_SIZE_6 6
    # define SEQ_SIZE_SEQ_SIZE_7 7
    # define SEQ_SIZE_SEQ_SIZE_8 8
    # define SEQ_SIZE_SEQ_SIZE_9 9
    # define SEQ_SIZE_SEQ_SIZE_10 10
    
    #define MAKE_VAR(elem)                         \
        float CONCAT(var_, elem) = 0;
    
    #define MAKE_LIST_0(op, list)
    #define MAKE_LIST_1(op, list)  op (HEAD(list)) MAKE_LIST_0(op, TAIL(list))
    #define MAKE_LIST_2(op, list)  op (HEAD(list)) MAKE_LIST_1(op, TAIL(list))
    #define MAKE_LIST_3(op, list)  op (HEAD(list)) MAKE_LIST_2(op, TAIL(list))
    #define MAKE_LIST_4(op, list)  op (HEAD(list)) MAKE_LIST_3(op, TAIL(list))
    #define MAKE_LIST_5(op, list)  op (HEAD(list)) MAKE_LIST_4(op, TAIL(list))
    #define MAKE_LIST_6(op, list)  op (HEAD(list)) MAKE_LIST_5(op, TAIL(list))
    #define MAKE_LIST_7(op, list)  op (HEAD(list)) MAKE_LIST_6(op, TAIL(list))
    #define MAKE_LIST_8(op, list)  op (HEAD(list)) MAKE_LIST_7(op, TAIL(list))
    #define MAKE_LIST_9(op, list)  op (HEAD(list)) MAKE_LIST_8(op, TAIL(list))
    #define MAKE_LIST_10(op, list)  op (HEAD(list)) MAKE_LIST_9(op, TAIL(list))
    
    #define MAKE_LIST(op, list) CONCAT(MAKE_LIST_, SEQ_SIZE(list)) (op, list)
    
    int main()
    {
        MAKE_LIST(MAKE_VAR, LIST)
    }
    

    在此基础上运行预处理器可获得以下结果:

    int main()
    {
        float var_1 = 0; float var_2 = 0; float var_3 = 0; float var_4 = 0; float var_5 = 0;
    }
    

    根据需要。我相信这可以简化一些,但我希望这会有所帮助。

        3
  •  0
  •   daminetreg    8 年前

    这是我的2美分:

    我记忆中的问题来自Boost中使用的预处理器元编程技术。预处理器是指在折叠序列时,不可能有任意长的元素列表。

    您需要有尽可能多的宏作为最大迭代,所以它可以是任意的,但也可以是最大的。

    然而,我想知道您是否可以扩展逗号,因为通常这是基于与stop condition宏或next iteration宏旁边的内容连接。我不知道如何从串联的宏中扩展逗号,因为串联将不再起作用。

    对于这种情况,如果您可以稍微更改一下API,我会做什么:

    #define EXPAND(...) __VA_ARGS__
    
    #define template_(X, Y)   \
      template<EXPAND X       \
        , int dummy = 0       \
      Y                       \
    >
    
    #define requires(...) \
      COMMA() std::enable_if_t< dummy == 0 && (__VA_ARGS__) > = 0  
    
    #define COMMA() ,
    

    因此,通过一个略微改变的API:

    template_((class A, class B, class C),
        requires(IsFoo<A> && IsBar<B>)
        requires(IsBaz<C>)
    )
    void frobozzle(A, B, C);
    

    它输出了所希望的:

    template<class A, class B, class C , 
      int dummy = 0 , 
      std::enable_if_t< dummy == 0 && (IsFoo<A> && IsBar<B>) > = 0 ,
      std::enable_if_t< dummy == 0 && (IsBaz<C>) > = 0 >
    void frobozzle(A, B, C);
    

    不完全是请求的API,但优点是,由于使用了requires宏,您可以使用带逗号的requires表达式 VA\U参数 :

    template_((class A, class B, class C),
        requires(IsBaseOf<B,C>)
    )
    int boo()
    

    我努力想把它无限地折叠起来,但看起来我够不着:D。

    我没有尝试的是复制与您提供的表达式模板相同的输入语法,这在我看来最终更加可行。