Bootstrap

尝试下使用 cpp 实现 Rust 的 enum

尝试下使用 cpp 实现 Rust 的 enum

长篇代码警告:本文建议边看边敲(),否则可能会犯困~~~

本文中代码运行需要支持,相信幼儿园小班就学过的你肯定能搞定编译环境的啦,么么哒~~~

一个小背景,Rust 中伪装成 enum 的 class

Rust 中的 enum 类型很强大,如下:

fn main() {
    enum Book {
        Papery {
            name: String,
            price: u32,
            author: String,
        },
        Electronic {
            name: String,
            url: String,
            author: String,
            filetype: String,
        },
    }

    let book = Book::Papery {
        name: String::from("C++变成死相"),
        price: 30,
        author: String::from("Zhang San"),
    };
    let ebook = Book::Electronic {
        name: String::from("加瓦入门到精通"),
        url: String::from("xxxx.xxx.xxx"),
        author: String::from("Li Si"),
        filetype: String::from("pdf"),
    };
    match book {
        Book::Papery {
            name,
            price,
            author,
        } => {
            println!(
                "Papery book name: {}, price:{} author: {}",
                name, price, author
            );
        }
        Book::Electronic {
            name,
            url,
            author,
            filetype,
        } => {
            println!(
                "E-book name: {} url:{} author: {} filetype:{}",
                name, url, author, filetype
            );
        }
    }

    match ebook {
        Book::Papery {
            name,
            price,
            author,
        } => {
            println!(
                "Papery book name: {}, price:{} author: {}",
                name, price, author
            );
        }
        Book::Electronic {
            name,
            url,
            author,
            filetype,
        } => {
            println!(
                "E-book name: {} url:{} author: {} filetype:{}",
                name, url, author, filetype
            );
        }
    }
}

如上所示, 中的 是可以带属性的,而且还可以带不止一种属性(这明明就是伪装成 的 么~~~),使用 可以对 进行匹配和提取(和 的泛型以及类型萃取很像有木有?), 还有一些其他的用法,这里先不考虑,仅仅考虑下用 实现上面例子中对 特性的展示。顺便复习下 久没用过的 !(好像也就一两天?)

如何实现?先列个计划!

毛爷爷说过:“用百折不回的毅力,有计划地克服所有的困难。”

我们现在就要克服用 实现 中 的困难,首先,第一步当然是给我们的新类型起个名字啦,既然是要扩展 类型,那么就叫 吧!反正起名一直是让程序员头疼的难题。有了名字,我们就要开始实现啦!

等等!说好的计划呢?

计划不是有么,分两步:1. 取名! 2. 实现! <_<

实现

第0步:搞一个类型来表示每个枚举项

从上面的例子可以看出, 的每个枚举项除了自身的名字外,还有一大波属性(姑且称之为 吧),它自身的名字,更像是一个类型,这里就把它叫做 吧。

一个类,包含一个索引类型和若干属性类型,用幼儿园中班就学过的可变参数模板就很容易实现,同时我们还可以顺便把它们萃取出来先放着,未雨绸缪嘛,以后总会用得着的。那么怎样在同一个对象里面表达多个类型呢?对!就是它,幼儿园小班就学过的 呀:

#pragma once

#include 

template 
struct enum_item {
    using index_type = IndexType;
    using value_type = std::tuple;
};

很容易就写出这样的代码啦, 瞬间具有了类型萃取能力,很方便有没有。

另外呢,为了防止一些用户丢一些带限定符呀引用呀等等等等乱七八糟的类型过来,我们对传入类型做一个简单的清理:

#pragma once

#include 

template 
struct enum_item {
    using index_type = typename std::decay::type;
    using value_type = std::tuple::type...>;
};

众所周知, 可以剥去类型的限定符外衣,让它赤裸裸地暴露在你面前。当然,如果你认为我上面这段代码有装逼嫌疑的话,也可以直接使用,不过由于我的不太喜欢这种东西(没事就给我显示个红色波浪线),这个逼我暂时就继续装着吧。

第1步:开始构造一个 enum_ex

上面的步骤,我们构造出了一个个的枚举项,现在我们要用一群枚举项实例化一个枚举类,大致使用就是这样:

struct Papery {};
struct Electronic {};

using Book = enum_ex<
    enum_item,
    enum_item
>;

这样就和 Rust 有那么一丢丢像啦 ^_^

那么,如何实现这个呢?聪明的幼儿园小班小盆友一定想到啦,当然是我们的可变参数模板啦:

template
class enum_ex{
};

But, But, But! 这里有个问题,如果用户瞎写:

using Book = enum_ex<
    int, 
    double,
    enum_item
>;

仿佛也可以编译通过呀,这可是个问题,那么有没有办法限定 的模板参数必须是 的实例化类型呢?当然有啦,我 这么能打,这都是小 啦,众所周知,在 前,可以通过来进行,不过我们已经有了 ,他的天生就是干这事的,我们使用对类型限制如下:

#pragma once

#include 
#include 

template 
struct enum_item {
    using index_type = typename std::decay::type;
    using value_type = std::tuple::type...>;
};

template 
struct is_enum_item : public std::false_type {
};

template 
struct is_enum_item> : public std::true_type {
};

template 
concept EnumTypeConcept = is_enum_item::value;

template 
requires(EnumTypeConcept&& ... && true) 
class enum_ex {
    
};

上面这个代码,相信幼儿园小班的小盆友们一定都看懵逼了,所以这可能需要幼儿园中班的知识了,上面的这一坨代码涉及了、、折叠表达式(),不懂的小盆友赶紧去 了解一下,否则会很印象进度的哦。

上面先使用将从实例化的类型摘出来,不是的走那个实例化,是的走那个实例化,然后把这个值绑定到 上。

在实例化时,使用对每个模板实参做校验,然后将所有的结果逻辑与连起来,形成最终校验。

其实还可以使用来校验,但是斯以为没有这种显式校验来的清晰。

第2步:如何从 IndexType 找到 enum_item

我们在的时候,可以通过一个索引类型直接提取出对应的属性值,所以第一步要解决的问题就是怎样从找到对应的。

template 
requires(EnumTypeConcept&&... && true) struct find_enum_item {
    using type = nullptr_t;
};

template 
    requires (EnumTypeConcept && ... && EnumTypeConcept) struct find_enum_item {
    using type = typename std::conditional<
        std::is_same::value,
        FirstType,
        typename find_enum_item::type>::type;
};

一样的套路,使用找到与类型集合的与给定一致的类型。

再对类型脱个衣服:

template 
requires(EnumTypeConcept&&... && true) struct find_enum_item {
    using type = nullptr_t;
};

template 
requires(EnumTypeConcept::type>&&...&& EnumTypeConcept::type>) struct find_enum_item {
    using type = typename std::conditional<
        std::is_same::type, typename FirstType::index_type>::value,
        typename std::decay::type,
        typename find_enum_item::type>::type;
};

写一段代测试一下:

#include "enum_ex.h"

#include 

struct Papery {
};
struct Electronic {
};

int main()
{
    static_assert(std::is_same<
                      typename find_enum_item<
                          Papery,
                          enum_item,
                          enum_item>::type,
                      enum_item>::value,
        "type mismatch");
}

一波编译, 0 error(s), 0 warning(s) ,Beautiful!

第3步:构造一个 enum_ex

我们希望我们的使用越简单越好,最好是可以这样:

using Book = enum_ex<
    enum_item,
    enum_item
>;

Book book("C++变成死相", 30, "Zhang San");

不过很遗憾,这种玩法是不可能了,因为类型是已经实例化的类型了,没办法使用来创建对象了。不过我们可以退而求其次,使用一个模板函数来创建对象。但是模板函数的参数列表不好处理(不信你试试?),我们可以换用成员变量,只不过他的类型是个仿函数。

那么问题又来了,函数签名应该长啥样呢?既然要创建类型,那肯定是返回一个啦,模板参数自然就是,形参类型也就是啦。所以我们需要一个新的类型来构造这个函数类型:


template 
struct is_tuple : std::false_type {
};

template 
struct is_tuple> : public std::true_type {
};

template 
concept TupleTypeConcept = is_tuple::value;

template 
requires(TupleTypeConcept&&...&& EnumTypeConcept) struct gen_func_type;

template 
requires(true && ... && EnumTypeConcept) struct gen_func_type, EnumTypes...> {
    using type = std::function(TupleTypes...)>;
};

这段代码很好理解,最前面那几坨就是加个的以及类前置声明,可以忽略。后面的那两坨就是传入索引类型、类型和枚举项的类型,生成一个以的形参类型作为参数类型,返回指定枚举类型的仿函数类型。

那么,怎么使用这个类呢?

是要作为模板参数传进来的,……不是现成的么。那么就还缺一个类型了。此时可以回顾一下我们在第0步的未雨绸缪了。废话不多说,看码:

template 
requires(EnumTypeConcept&&... && true) class enum_ex {
public:
    template 
    static typename gen_func_type::type::value_type,
        EnumTypes...>::type make_enum;
};

template 
template 
typename gen_func_type::type::value_type,
    EnumTypes...>::type
    enum_ex::make_enum
    = nullptr;

此时和已经准备就绪,然后我们再使用第2步的从和找到的和第0步的类型萃取,轻轻松松就拿到了生成的仿函数类型了。我们用它定义一个静态变量。目前我们还没有想好怎么存储这玩意,姑且给它赋值为吧。这里需要注意一点就是这个类型他不是一个常量表达式,不能进行类内初始化,所以必须把他的定义写到类外面,就是最后那一坨。

我们写个小片段测试下编译情况吧:

#include "enum_ex.h"

#include 

struct Papery {
};
struct Electronic {
};

using Book = enum_ex<
    enum_item,
    enum_item>;

int main()
{
    Book::make_enum("C++变成死相", 30, "Zhang San");
}

编译妥妥的。

到这里呢,代码已经变成这样啦(为了防止小班同学跟不上,贴一次完整代码,这绝对不是凑字数哈~~~):

#pragma once

#include 
#include 
#include 
#include 

template 
struct enum_item {
    using index_type = typename std::decay::type;
    using value_type = std::tuple::type...>;
};

template 
struct is_enum_item : public std::false_type {
};

template 
struct is_enum_item> : public std::true_type {
};

template 
concept EnumTypeConcept = is_enum_item::value;

template 
requires(EnumTypeConcept&&... && true) struct find_enum_item {
    using type = nullptr_t;
};

template 
requires(EnumTypeConcept::type>&&...&& EnumTypeConcept::type>) struct find_enum_item {
    using type = typename std::conditional<
        std::is_same::type, typename FirstType::index_type>::value,
        typename std::decay::type,
        typename find_enum_item::type>::type;
};

template 
requires(EnumTypeConcept&&... && true) class enum_ex;

template 
struct is_tuple : std::false_type {
};

template 
struct is_tuple> : public std::true_type {
};

template 
concept TupleTypeConcept = is_tuple::value;

template 
requires(TupleTypeConcept&&...&& EnumTypeConcept) struct gen_func_type;

template 
requires(true && ... && EnumTypeConcept) struct gen_func_type, EnumTypes...> {
    using type = std::function(TupleTypes...)>;
};

template 
requires(EnumTypeConcept&&... && true) class enum_ex {
public:
    template 
    static typename gen_func_type::type::value_type,
        EnumTypes...>::type make_enum;
};

template 
template 
typename gen_func_type::type::value_type,
    EnumTypes...>::type
    enum_ex::make_enum
    = nullptr;

突破50行了,很刺激有木有?

第4步:把 enum 的属性存起来

这一步就很简单了,就是要把这个函数传进来的参数存起来,后面用的时候还能找到。从上文可知,这个函数接受的参数那可是千奇百怪了,不同类型不同长度都有,那咋办,当然是先填到中然后扔到宽容一切的里啦:


template 
requires(EnumTypeConcept&&... && true) class enum_ex {
private:
private:
    std::any data__;

    template 
    requires(TupleTypeConcept)
        enum_ex(TupleType tp)
        : data__(tp)
    {
    }

public:
    template 
    static typename gen_func_type::type::value_type,
        EnumTypes...>::type make_enum;
};

template 
template 
typename gen_func_type::type::value_type,
    EnumTypes...>::type
    enum_ex::make_enum
    = typename gen_func_type::type::value_type,
        EnumTypes...>::type([](auto... f) {
          return enum_ex(
              typename find_enum_item::type::value_type(f...));
      });

这里呢,用到了一丢丢泛型,毕竟要真的写类型,才疏学浅的我也不知道怎么写。

上面这一坨代码呢,首先我们把的构造函数实现了(难得呀),然后直接将传进来的参数包成一个丢给的构造函数,的构造函数将参数丢给对象存起来。

第5步:把属性取出来

上一步把属性存了,现在该把属性取出来了,首先回头看看是怎么取的,用关键字匹配一个枚举,然后符合某种模式的将属性取出来,然后处理这些属性。的话,没有……那就写个差不多的吧:

template 
requires(TupleTypeConcept&&...&& EnumTypeConcept) struct apply_type;

template 
requires(true && ... && EnumTypeConcept) struct apply_type, EnumTypes...> {
    using type = std::function;
};

template 
requires(EnumTypeConcept&&... && true) class enum_ex {
private:
    std::any data__;

    template 
    requires(TupleTypeConcept)
        enum_ex(TupleType tp)
        : data__(tp)
    {
    }

public:
    template 
    static typename gen_func_type::type::value_type,
        EnumTypes...>::type make_enum;

    template 
    enum_ex& $(typename apply_type::type::value_type, EnumTypes...>::type f)
    {
        try {
            std::apply(f, std::any_cast::type::value_type>(data__));
        } catch (const std::bad_cast&) {
        }
        return *this;
    }
};

这段代码的上面那一坨是构造出一个回调函数类型仿函数的类,然后模板函数(没错,就是函数名,毕竟键盘上的符号已经被用光了)以作为模板形参,尝试匹配中存储的类型,如果匹配上,就调用回调函数,展开作为函数参数的用法幼儿园小班已经烂熟于心啦,就是嘛。至于依旧返回,当然是为了链式调用,把它串起来啦。

写段代码测试一下:

#include "enum_ex.h"

#include 
#include 

struct Papery {
};
struct Electronic {
};

using Book = enum_ex<
    enum_item,
    enum_item>;

int main()
{
    auto e = Book::make_enum("C++变成死相", 30, "Zhang San");

    e
        .$([](auto name, auto price, auto author) {
            std::cout << "Papery book name: " << name << ", price:" << price << " author:" << author << std::endl;
        })
        .$([](auto name, auto url, auto author, auto filetype) {
            std::cout << "E-book name: " << name << " url:" << url << " author: " << author << " filetype:" << filetype << std::endl;
        });
}

成功编译并运行,老哥,稳!

再贴一次当前的实现代码:

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 

template 
struct enum_item {
    using index_type = typename std::decay::type;
    using value_type = std::tuple::type...>;
};

template 
struct is_enum_item : public std::false_type {
};

template 
struct is_enum_item> : public std::true_type {
};

template 
concept EnumTypeConcept = is_enum_item::value;

template 
requires(EnumTypeConcept&&... && true) struct find_enum_item {
    using type = nullptr_t;
};

template 
requires(EnumTypeConcept::type>&&...&& EnumTypeConcept::type>) struct find_enum_item {
    using type = typename std::conditional<
        std::is_same::type, typename FirstType::index_type>::value,
        typename std::decay::type,
        typename find_enum_item::type>::type;
};

template 
requires(EnumTypeConcept&&... && true) class enum_ex;

template 
struct is_tuple : std::false_type {
};

template 
struct is_tuple> : public std::true_type {
};

template 
concept TupleTypeConcept = is_tuple::value;

template 
requires(TupleTypeConcept&&...&& EnumTypeConcept) struct gen_func_type;

template 
requires(true && ... && EnumTypeConcept) struct gen_func_type, EnumTypes...> {
    using type = std::function(TupleTypes...)>;
};

template 
requires(TupleTypeConcept&&...&& EnumTypeConcept) struct apply_type;

template 
requires(true && ... && EnumTypeConcept) struct apply_type, EnumTypes...> {
    using type = std::function;
};

template 
requires(EnumTypeConcept&&... && true) class enum_ex {
private:
    std::any data__;

    template 
    requires(TupleTypeConcept)
        enum_ex(TupleType tp)
        : data__(tp)
    {
    }

public:
    template 
    static typename gen_func_type::type::value_type,
        EnumTypes...>::type make_enum;

    template 
    enum_ex& $(typename apply_type::type::value_type, EnumTypes...>::type f)
    {
        try {
            std::apply(f, std::any_cast::type::value_type>(data__));
        } catch (const std::bad_cast&) {
        }
        return *this;
    }
};

template 
template 
typename gen_func_type::type::value_type,
    EnumTypes...>::type
    enum_ex::make_enum
    = typename gen_func_type::type::value_type,
        EnumTypes...>::type([](auto... f) {
          return enum_ex(
              typename find_enum_item::type::value_type(f...));
      });

第6步:一丢丢优化

上面的代码有很多可以优化的地方,包括但不限于:

  • 对多个类型限定的可以写成一个,不用

  • 使用和消除和

  • 使用类型自动推导,不显式写出类型

优化后的代码长这样:

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 

template 
struct enum_item {
    using index_type = std::decay_t;
    using value_type = std::tuple...>;
};

template 
struct is_enum_item : public std::false_type {
};

template 
struct is_enum_item> : public std::true_type {
};

template 
constexpr bool is_enum_item_v = is_enum_item::value;

template 
concept EnumTypesConcept = (is_enum_item_v && ... && true);

template 
requires EnumTypesConcept struct find_enum_item {
    using type = nullptr_t;
};

template 
requires EnumTypesConcept..., std::decay_t> struct find_enum_item {
    using type = std::conditional_t<
        std::is_same, typename FirstType::index_type>::value,
        std::decay_t,
        typename find_enum_item::type>;
};

template 
using find_enum_item_t = typename find_enum_item::type;

template 
using find_enum_item_vt = typename find_enum_item_t::value_type;

template 
requires EnumTypesConcept class enum_ex;

template 
struct is_tuple : std::false_type {
};

template 
struct is_tuple> : public std::true_type {
};

template 
concept TupleTypeConcept = is_tuple::value;

template 
requires TupleTypeConcept&& EnumTypesConcept struct gen_func_type;

template 
requires EnumTypesConcept struct gen_func_type, EnumTypes...> {
    using type = std::function(TupleTypes...)>;
};

template 
requires TupleTypeConcept&& EnumTypesConcept struct apply_type;

template 
requires EnumTypesConcept struct apply_type, EnumTypes...> {
    using type = std::function;
};

template 
using apply_type_t = typename apply_type::type;

template 
requires EnumTypesConcept class enum_ex {
private:
    std::any data__;

    template 
    requires(TupleTypeConcept)
        enum_ex(TupleType tp)
        : data__(tp)
    {
    }

public:
    template 
    static typename gen_func_type,
        EnumTypes...>::type make_enum;

    template 
    enum_ex& $(apply_type_t, EnumTypes...> f)
    {
        try {
            std::apply(f, std::any_cast>(data__));
        } catch (const std::bad_cast&) {
        }
        return *this;
    }
};

template 
template 
typename gen_func_type,
    EnumTypes...>::type
    enum_ex::make_enum
    = [](auto... f) {
          return enum_ex(
              find_enum_item_vt(f...));
      };

另外附上一坨使用示例:

#include "enum_ex.h"

#include 
#include 

struct Papery {
};
struct Electronic {
};

using Book = enum_ex<
    enum_item,
    enum_item>;

int main()
{
    auto e = Book::make_enum("C++变成死相", 30, "Zhang San");
    auto f = Book::make_enum("加瓦入门到精通", "xxxx.xxx.xxx", "Li Si", "pdf");

    auto print_papery = [](auto name, auto price, auto author) {
        std::cout << "Papery book name: " << name << ", price:" << price << " author:" << author << std::endl;
    };

    auto print_electronic = [](auto name, auto url, auto author, auto filetype) {
        std::cout << "E-book name: " << name << " url:" << url << " author: " << author << " filetype:" << filetype << std::endl;
    };

    e
        .$(print_papery)
        .$(print_electronic);
    f
        .$(print_papery)
        .$(print_electronic);
}

和开头的代码遥相呼应哦。

后面还可以继续往下做哦,比如可以这样:

auto [name, price, author] = e.prop();

聪明的小盆友一定知道怎么实现啦(小提示:结构化绑定),反正我已经不想写了,现代的世界就是这么朴实无华,且枯燥~~~

洗洗睡觉~~~