ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++ 元编程之代码生成:设计并实现拓扑结构 DSL

2020-09-08 15:04:09  阅读:242  来源: 互联网

标签:代码生成 task struct C++ DSL template Context using type


对比

来看看这幅图,


 

原先方式是需要这样描述:

tf::Taskflow taskflow;

tf::Task A = taskflow.emplace([] () {}).name("A");

tf::Task B = taskflow.emplace([] () {}).name("B");

tf::Task C = taskflow.emplace([] () {}).name("C");

tf::Task D = taskflow.emplace([] () {}).name("D");

tf::Task E = taskflow.emplace([] () {}).name("E");

A.precede(B, C, E);

C.precede(D);

B.precede(D, E);

用 DSL 之后只需要这样,代码更直观:

def_task(A, { std::cout << "TaskA\n"; };);

def_task(B, { std::cout << "TaskB\n"; };);

def_task(C, { std::cout << "TaskC\n"; };);

def_task(D, { std::cout << "TaskD\n"; };);

def_task(E, { std::cout << "TaskE\n"; };);

taskbuild(

  task(A)

    -> fork(B, C)

    -> task(D),

  merge(A, B)

    -> task(E)

)(tf);

目前支持的特性、原因如下:

def_task 定义一个 task

def_taskc 定义一个带 context 数据的 task

taskbuild 描述 task 间的连接关系,按需传递 context 数据

fork / merge 表示任意多个 task,而 task() 表示一个 task

fork(a,b,c,d) -> merge(e,f,g,h) 会生成笛卡尔积一共 16 条连接关系

__task(a) -> __task(b) -> __task(c) -> ... 能够通过 -> 描述任意长度的连接关系

关键实现

DSL 文法如下:

<def_task>  ::= <TaskSignature>

<taskbuild> ::= <Chains>

<fork>      ::= <SomeTask>

<merge>    ::= <SomeTask>

<Chains>    ::= <Link>

              | <Link>, <Chains>

<Link>      ::= <Tasks> -> <Tasks>

              | <Tasks> -> <Link>

<Tasks>    ::= <SomeTask>

<SomeTask>  ::= <TaskSignature>

              | <TaskSignature>, <SomeTask>

              | <SomeTask>

通过上述规则可以构建 TaskDSL 对象,其对象主要主要由两大成员组成:

TasksCB 异构容器,用于存放每个 TaskSignature ,而 TaskSignature 的职责就是 Task 工厂,用于创建具体的 task,然后存放于传递的 Taskflow 对象中,其拥有 task 的所有权。

OneToOneLinkInstances 异构容器,由 TaskAnalyzer 模块推断出来的 task 一对一关系,其方法 build 用于生成 From.precede(To) 代码。

TasksCB

这里需要考虑一个问题,如何将 context 数据通过 TaskSignature 工厂传递给 task 呢?而 task 运行时不需要看到 context 变量就好了,就像 lambda 捕获那种效果?

这里就需要发挥多继承的魅力了,我将 Context 类和 TaskSignature 通过多继承拼装到一块去来解决这个问题:

#define def_taskc(name, Context, ...)                  \

  struct name : TaskSignature, Context {              \

    name(const Context &context) : Context(context) {} \

    auto operator()() {                                \

      /* copy *this(copy CONTEXT to lambda) */        \

      return [*this] __VA_ARGS__;                      \

    }                                                  \

  };

创建 task 的时候,捕获了 *this ,而 *this 蕴含着 Context 数据。之所以需要 *this 而不是 this ,是因为需要将 Context 数据拷贝到 task 这个 lambda 中,因为一旦 task 创建完成后, TaskSignature 对象就析构,从而导致悬挂指针,而拷贝避免了这个问题,这也要求 Context 类是 Copyable 的。

OneToOneLinkInstances

之所以能得到 task(A)->task(B)->task(C)->task(D)... 的 chain 效果,其本质背后是函数签名类型: auto(*)(A) -> auto(*)(B) -> auto(*)(C) -> auto(*)(D) -> void ,这是一个高阶函数,输入 A 得到一个函数,输入其参数 B 得到一个函数,以此类推,最终得到 void。而最终的 void 是必不可少的,因为 C++ 规范要求模板参数不能全为 auto,在 clang11 支持这个特性,而 clang12 把这个 bug 修好了 = = 为了避免用户需要每次在最后指明 ->void ,我用 REPEAT 宏自动给每条 chain 后面加上 ->void 。

这个一连串的效果还是我突然得到的灵感,给了我很大的惊喜,这样就可以用深度优先的方式描述了,而不仅仅局限于广度优先。C++ 实在是太有意思了:joy:。

得到 chain 后,需要将其解构成 (Connection, ->) 的偏序集。这里我很巧妙的用了模板萃取技术得到:

// 输入 Chain,输出偏序集 OUT: (Connection, ->)

template<typename T, typename OUT = TypeList<>>

struct Chain;

// 模式匹配,边界情况,返回结果

template<typename F, typename OUT>

struct Chain<auto(*)(F) -> void, OUT> {

    using From = F;

    using type = OUT;

};

// 从头往后取 Tasks,构成一个个 Connection

template<typename F, typename T, typename OUT>

struct Chain<auto(*)(F) -> T, OUT> {

private:

    using To = typename Chain<T, OUT>::From;

public:

    using From = F;

    using type = typename Chain

        <T, typename OUT::template appendTo<Connection<From, To>>>::type;

};

有了 (Connection, ->) 偏序集,还需要从中得到一对一的关系,比如 Connection<SomeTask<A, B>, SomeTask<C>> 应该得到笛卡尔积关系: OneToOneLink<A, C> , OneToOneLink<B, C> ,下面是求笛卡尔积代码:

// 输入 <FromTasks, ToTasks>, 输出 OneToOneLinkSet

template<typename FROMs, typename TOs, typename = void>

struct BuildOneToOneLink;

template<typename ...Fs, typename Ts>

struct BuildOneToOneLink<TypeList<Fs...>, Ts> {

    using type = Concat_t<typename BuildOneToOneLink<Fs, Ts>::type...>;

};

template<typename F, typename... Ts>

struct BuildOneToOneLink<F, TypeList<Ts...>,

                        std::enable_if_t<!IsTypeList_v<F>>> {

    using type = TypeList<OneToOneLink<F, Ts>...>;

};

最后去重得到 OneToOneLinkInstances ,从而生成一条条 precede 语句,而这些都是在编译期完成的。

结论

从以上探索、实现过程发现,C++ 其实是一门很有意思的语言,没有约束能给人很多想象空间,可玩性也更强,这也对程序猿有极高的要求,才能 hold 得住这么一个怪物:grinning:

而由于其编译期元编程是偶然发现的,没有经过专门设计,导致语法过于复杂,但是其实和 FP 语言思路写法很相似,比如我实现一个快排,不考虑优化,在 Haskell 我会这么写:

quicksort :: (Ord a) => [a] -> [a]

quicksort [] = []

quicksort (x:xs) =

  let smallerSorted = quicksort [a | a <- xs, a <= x]

      biggerSorted  = quicksort [a | a <- xs, a > x]

  in  smallerSorted ++ [x] ++ biggerSorted

而在 C++ 中,思路是一样的,我这么实现:

template<typename IN, template<typename, typename> class CMP>

struct QuickSort {

    using type = TypeList<>;

};

template<typename IN, template<typename, typename> class CMP>

using QuickSort_t = typename QuickSort<IN, CMP>::type;

template<template<typename, typename> class CMP, typename H, typename ...Ts>

class QuickSort<TypeList<H, Ts...>, CMP> {

    template<typename E>

    struct LT { static constexpr bool value = CMP<E, H>::value; };

    using P = Partition_t<TypeList<Ts...>, LT>;

// smallerSorted = quicksort [a | a <- xs, a <= x]

    using SmallerSorted = QuickSort_t<typename P::satisfied, CMP>;

// biggerSorted  = quicksort [a | a <- xs, a> x]

    using BiggerSorted = QuickSort_t<typename P::rest, CMP>;

public:

// smallerSorted ++ [x] ++ biggerSorted

    using type = Concat_t<

        typename SmallerSorted::template appendTo<H>, BiggerSorted

    >;

};

这也是玩 C++ 的乐趣所在,很自由的表达一切。

C语言就是一个既可以强化思维能力,又可以打好编程基础的编程语言,对这个感兴趣的同学来小编这里学学吧。即使是零基础的学习者,都可以一起成长进步。

标签:代码生成,task,struct,C++,DSL,template,Context,using,type
来源: https://www.cnblogs.com/mu-ge/p/13632522.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有