当前位置: 首页 > news >正文

C++26 编译时反射简介

什么是反射?

反射是指程序观察自身的结构, 并且可以获取到有关它的信息. 比如获取结构体的字段(Field/Member)及其类型, 获取方法(Method), 检查是否存在特定的方法.

反射可以用来做代码生成, 它可以大大减少样板代码. 使用场景有: 结构体的序列化和反序列化, 可以避免繁琐的手写代码. 在日常业务中, C++ 解析json或者xml时, 需要手写序列化/反序列化的代码, 这个过程需要很多的重复代码, 且没有多少技术含量.

当前的提议

当前的反射功能的提议为: P2996 “Reflection for C++26”, Daveed Vandervourde, Wyatt Childers, Peter Dimov, Dan Katz, Barry Rezvin, Andrew Sutton, Faisil Vali.

这个提案基于为 P1240 所做的工作, 是一个最小可行提案, 是一个编译时且基于函数的 API, 所有函数都是 consteval.

主要有三部分组成:

  1. 反射信息std::meta::info. 是不透明标量类型, 保存的是反射得到的信息. 一切反射结果都使用一种类型, 仅在编译时有用.
  2. 反射操作符^. 用来执行反射操作.
  3. Splicer [: :]. 将反射值转换为代码. 用来将反射操作符得到的值应用到代码中.

反射操作符 ^

反射操作符^用来将语法构造转换为反射值, 它是前缀一元操作符, 它的返回值是一个 std::meta::info 对象.

可以用在如下场景:

  1. 类型

    constexpr std::meta::info r1 = ^int;
    constexpr std::meta::info r2 = ^std::vector<int>;
    constexpr std::meta::info r3 =  ^std::string;
    
  2. 命名空间, 包括全局命名空间::

    constexpr std::meta::info r4 = ^::;
    constexpr std::meta::info r5 = ^std::chrono;
    
  3. 常量表达式

    constexpr std::meta::info r6 = ^(std::barrier<>::max() - 100);
    
  4. 一个符号名: 函数, 变量, 结构化绑定, 模板, 概念

    constexpr std::meta::info r7 = ^std::vector;
    constexpr std::meta::info r8 = ^std::fopen;
    

Splicer [: :]

用来将反射值转换为代码, 它的操作数是一个 std::meta::info 对象, 有时候也可以存在前置的typename或者template.

  1. 内置类型:

    #include <experimental/meta>
    #include <iostream>
    #include <string>
    #include <type_traits>
    #include <vector>
    
    int main() {
      //{
      constexpr auto r = ^int;
      typename[:r:] x = 42;          // 等价于: int x = 42;
      std::vector<typename[:r:]> v;  // 等价于: std::vector<int> v;
    
      typename[:^double:] d = 3.14;  // 等价于: double d = 3.14;
    
      std::cout << "x=" << x << ", d=" << d << std::endl;
      //}
    }
    

    EDG 和 NVC++ 24.3 目前能编译通过, 点击链接在 Compiler Explorer 中查看.

  2. 函数类型

    #include <experimental/meta>
    #include <iostream>
    
    int f(int a, int b) { return a + b; }
    constexpr auto func = ^f;
    int main() {
      int (*fp)(int, int) = &[:func:];
      std::cout << [:func:](1, 2) + fp(3, 4);
    }
    

    在 Compiler Explorer 中查看: NVC++ 24.3

  3. 结构体成员

    #include <experimental/meta>
    #include <iostream>
    
    struct S {
      int field;
    };
    int main() {
      constexpr auto member = ^S::field;
      S a;
      a.[:member:] = 42;
      std::cout << "a." << std::meta::name_of(member) << "=" << a.[:member:];
    }
    

    在 Compiler Explorer 中查看: NVC++ 24.3

元函数

元函数的输入为std::meta::info, 用来获取反射值相关的信息. 目前有如下的元函数:

consteval bool is_namespace(info r);
consteval bool is_function(info r);
consteval bool is_variable(info r);
consteval bool is_type(info r);
consteval bool is_alias(info r);
consteval bool is_template(info r);
consteval bool is_concept(info r);
consteval bool is_class_member(info r);
consteval bool is_base(info r);
consteval string_view name_of(info r);
consteval string_view qualified_name_of(info r);
consteval string_view display_name_of(info r);
consteval source_location source_location_of(info r);

下面的代码展示了调用一个结构体的size() 或者 length()方法.

template <typename T>
int size_or_length(T&& x) {
  if constexpr (std::is_class_v<T>) {
    template for (constexpr auto memfunc :
                  members_of(^T, std::meta::is_function)) {
      if constexpr ((name_of(memfunc) == "size" ||
                     name_of(memfunc) == "length") &&
                    requires(T y) {
                      { y.[:memfunc:]() } -> std::integral;
                    }) {
        return x.[:memfunc:]();
      }
    };
  }
  return -1;
}

使用样例

1. 枚举转字符串

#include <experimental/meta>
#include <string>
#include <type_traits>

template<typename E>
  requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
  std::string result = "<unnamed>";
  [:expand(std::meta::enumerators_of(^E)):] >>
  [&]<auto e>{
    if (value == [:e:]) {
      result = std::meta::identifier_of(e);
    }
  };
  return result;
}

enum Color { red, green, blue };
static_assert(enum_to_string(Color::red) == "red");
static_assert(enum_to_string(Color(42)) == "<unnamed>");

在 Compiler Explorer 中在线运行.

2. 解析命令行参数

#include <string>
#include <experimental/meta>
#include <iostream>
#include <algorithm>
#include <spanstream>
#include <type_traits>

template<typename Opts>
Opts parse_options(int argc, char** argv) {
  std::vector<std::string_view> args(argv + 1, argv + argc);
  Opts opts;

  [: expand(nonstatic_data_members_of(^Opts)) :] >> [&]<auto dm>{
    auto it = std::find_if(args.begin(), args.end(),
      [](std::string_view arg){
        return arg.starts_with("--") && arg.substr(2) == identifier_of(dm);
      });
    if (it == args.end()) {
      return;
    }
    using T = typename[:type_of(dm):];
    if constexpr (std::is_same_v<T, bool>) {
      opts.[:dm:] = true;
    } else if (it + 1 == args.end()) {
      std::cerr << "Missing value for option " << *it << "\n";
    } else {
      auto iss = std::ispanstream(it[1]);
      if (iss >> opts.[:dm:]; !iss) {
        std::cerr << "Invalid value \"" << it[1] << "\" for option " << *it << "\n";
      }
    }
  };
  return opts;
}

struct app_options {
  int iterations = 1'000;
  int size = 25'000;
  bool verbose = false;
  std::string inputFile = "input.dat";
};

int main(int argc, char *argv[]) {
  app_options opts = parse_options<app_options>(argc, argv);

  std::cout << "iterations: " << opts.iterations << "\n"
            << "size: " << opts.size << "\n"
            << "verbose: " << opts.verbose << "\n"
            << "inputFile: " << opts.inputFile << "\n";
}

在 Compiler Explorer 中在线运行.

总结

  • C++ 反射功能即将到来
  • C++ 反射是编译时的功能
  • C++反射能够减少手工编写样板代码, 减少重复代码.

参考资料

  1. C++ Reflection - Back on Track by David Olsen
  2. Reflection for C++26

延伸阅读

  1. C++26 新特性预览

相关文章:

  • 0101数学_算法-常用符号-常用模板.md
  • stm32week9
  • 【Es】基础入门:开启全文搜索的大门
  • 数据结构每日一题day4(顺序表)★★★★★
  • conda的基础命令
  • VLAN的高级特性
  • 苹果与安卓,鸿蒙下跨设备,应用分享
  • Matlab_Simulink中导入CSV数据与仿真实现方法
  • 基于Spring Boot的木里风景文化管理平台的设计与实现(LW+源码+讲解)
  • 1.2 斐波那契数列模型:LeetCode 面试题 08.01. 三步问题
  • 【NLP】13. NLP推理方法详解 --- 穷举和贪心搜索
  • Hyperlane 框架的临时上下文数据功能:助力高效 Web 开发
  • NoSQL数据库
  • JS 手撕题高频考点
  • MATLAB详细图文安装教程(附安装包)
  • 域渗透(web安全)
  • 归并排序与快速排序的简单实现(C语言)
  • 前端自动创建react项目脚手架
  • 中国太平保险网申校招测评笔试真题分析、历年真题题库、北森答案解析
  • golang 日志log与logrus
  • 学设计师培训班/杭州网站优化方案
  • 移动网站 案例/郑州网络营销推广公司
  • 如何防止网站挂黑链/百度官网app
  • 品牌宣传推广文案/合肥优化推广公司
  • b2b电子商务网站调研报告一千字/中央电视台新闻联播
  • 做淘宝客建网站要多少费用/东莞网络推广公司