2411C++,C++26反射示例

参考

namespace __impl {
  template<auto... vals>
  struct replicator_type {
    template<typename F>
      constexpr void operator>>(F body) const {
        (body.template operator()<vals>(), ...);
      }
  };
  template<auto... vals>
  replicator_type<vals...> replicator = {};
}
template<typename R>
consteval auto expand(R range) {
  std::vector<std::meta::info> args;
  for (auto r : range) {
    args.push_back(reflect_value(r));
  }
  return substitute(^__impl::replicator, args);
}

用法:

 //用`扩展`语句
template <typename E>
  requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
  template for (constexpr auto e : std::meta::enumerators_of(^E)) {
    if (value == [:e:]) {
      return std::string(std::meta::identifier_of(e));
    }
  }
  return "<unnamed>";
}

 //使用`扩展`解决方法
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;
}

示例

3.1来回

第一例并不引人注目,而是要展示如何在反射域和语法域来回切换:

constexpr auto r = ^int;
typename[:r:] x = 42;       //等价于:`int x=42;`
typename[:^char:] c = '*';  //等价于:`charc='*';`

在与依赖全名相同环境中,即,在标准叫做仅类型的环境中,可省略型名前缀.如:

using MyType = [:sizeof(int)<sizeof(long)? ^long : ^int:];  //隐式`"型名"`前缀.

3.2选择成员

第二个示例允许为特定类型"按序号"选择成员:

struct S { unsigned i:2, j:6; };
consteval auto member_number(int n) {
  if (n == 0) return ^S::i;
  else if (n == 1) return ^S::j;
}
int main() {
  S s{0, 0};
  s.[:member_number(1):] = 42;  //等价于:`s.j=42;`
  s.[:member_number(5):] = 0;   //错误`(member_number(5))`不是一个常数.
}

此例还说明了可以访问字段.

注意,像s.[:member_number(1):]此"访问成员拼接"是比传统语法更直接的访问成员机制.它不涉及查找成员名,检查访问,或如果拼接反射值表示成员函数解析重载.

该提案包括许多常值"元函数",可用它们内省各种语言结构.这些元函数包括,描述给定类型非静态成员返回一个反射值向量std::meta::nonstatic_data_members_of.
因此,可重写上例为:

struct S { unsigned i:2, j:6; };
consteval auto member_number(int n) {
  return std::meta::nonstatic_data_members_of(^S)[n];
}
int main() {
  S s{0, 0};
  s.[:member_number(1):] = 42;  //等价于:`s.j=42;`
  s.[:member_number(5):] = 0;   //错误`(member_number(5))`不是一个常数.
}

此提案指定std::meta名字空间与(std::meta::info)反射类型关联;因此,在上例中可省略std::meta::限定.

另一个经常有用元函数是返回一个,描述声明给定反射值表示的实例的std::string_view标识的std::meta::identifier_of.

有了此工具,可按"串"访问非静态数据成员:

struct S { unsigned i:2, j:6; };
consteval auto member_named(std::string_view name) {
  for (std::meta::info field : nonstatic_data_members_of(^S)) {
    if (has_identifier(field) && identifier_of(field) == name)
      return field;
  }
}
int main() {
  S s{0, 0};
  s.[:member_named("j"):] = 42;  //等价于:`s.j=42;`
  s.[:member_named("x"):] = 0;   //错误`(member_named("x")`不是一个常数.
}

3.3类型列表到大小列表

在此,大小是一个用{sizeof(int),sizeof(float),sizeof(double)}初化的std::array<std::size_t,3>:

constexpr std::array types = {^int, ^float, ^double};
constexpr std::array sizes = []{
  std::array<std::size_t, types.size()> r;
  std::views::transform(types, r.begin(), std::meta::size_of);
  return r;
}();

比较此方法与以下基于类型生成相同数组大小的方法:

template<class...> struct list {};
using types = list<int, float, double>;
constexpr auto sizes = []<template<class...> class L, class... T>(L<T...>) {
    return std::array<std::size_t, sizeof...(T)>{{ sizeof(T)... }};
}(types{});

3.4实现make_integer_sequence

与使用平凡模板元编程手动方法相比,尽管今天的标准库依赖内部函数,可提供更好make_integer_sequence实现:

#include <utility>
#include <vector>
template<typename T>
consteval std::meta::info make_integer_seq_refl(T N) {
  std::vector args{^T};
  for (T k = 0; k < N; ++k) {
    args.push_back(std::meta::reflect_value(k));
  }
  return substitute(^std::integer_sequence, args);
}
template<typename T, T N>
  using make_integer_sequence = [:make_integer_seq_refl<T>(N):];

注意,替换模板过程中隐式缓存仍适用.因此,多次使用make_integer_sequence<int,20>计算只涉及一次make_integer_seq_refl<int>(20).

3.5取类布局

struct member_descriptor
{
  std::size_t offset;
  std::size_t size;
};
//返回`std::array<member_descriptor,N>`
template <typename S>
consteval auto get_layout() {
  constexpr auto members = nonstatic_data_members_of(^S);
  std::array<member_descriptor, members.size()> layout;
  for (int i = 0; i < members.size(); ++i) {
      layout[i] = {.offset=offset_of(members[i]).bytes, .size=size_of(members[i])};
  }
  return layout;
}
struct X
{
    char a;
    int b;
    double c;
};
/*`常式`*/ auto Xd = get_layout<X>();

/*其中`Xd`将是`std::array<member_descriptor,3>{{{0,1},{4,4},{8,8}}}`*/

3.6枚举转串

最常见工具之一按串转换枚举值,此例依赖扩展语句:

template <typename E>
  requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
  template for (constexpr auto e : std::meta::enumerators_of(^E)) {
    if (value == [:e:]) {
      return std::string(std::meta::identifier_of(e));
    }
  }
  return "<unnamed>";
}
enum Color { red, green, blue };
static_assert(enum_to_string(Color::red) == "red");
static_assert(enum_to_string(Color(42)) == "<unnamed>");

也可反向:

template <typename E>
  requires std::is_enum_v<E>
constexpr std::optional<E> string_to_enum(std::string_view name) {
  template for (constexpr auto e : std::meta::enumerators_of(^E)) {
    if (name == std::meta::identifier_of(e)) {
      return [:e:];
    }
  }
  return std::nullopt;
}

但是不必使用扩展语句,也可用算法.如,enum_to_string也可这样实现,此例依赖非瞬态常式分配,这也演示了根据枚举器个数选择不同算法:

template <typename E>
  requires std::is_enum_v<E>
constexpr std::string enum_to_string(E value) {
  constexpr auto get_pairs = []{
    return std::meta::enumerators_of(^E)
      | std::views::transform([](std::meta::info e){
          return std::pair<E, std::string>(std::meta::extract<E>(e), std::meta::identifier_of(e));
        })
  };
  constexpr auto get_name = [](E value) -> std::optional<std::string> {
    if constexpr (enumerators_of(^E).size() <= 7) {
      //如果枚举器不多,请使用`find_if()`的向量
      constexpr auto enumerators = get_pairs() | std::ranges::to<std::vector>();
      auto it = std::ranges::find_if(enumerators, [value](auto const& pr){
        return pr.first == value;
      };
      if (it == enumerators.end()) {
        return std::nullopt;
      } else {
        return it->second;
      }
    } else {
      //如果有很多枚举器,请使用`find()`的`映射`
      constexpr auto enumerators = get_pairs() | std::ranges::to<std::map>();
      auto it = enumerators.find(value);
      if (it == enumerators.end()) {
        return std::nullopt;
      } else {
        return it->second;
      }
    }
  };
  return get_name(value).value_or("<unnamed>");
}

在编译时,可根据enumerators_of长度选择更复杂查找算法(^E)

可生成紧凑双向持久数据结构,以最小的消费空间同时支持enum_to_stringstring_to_enum等.

3.7解析命令行选项

下一例展示了命令行选项解析器,如何根据成员名自动推导标志来工作.真正的命令行解析器当然会更复杂,这仅是个开始.

template<typename Opts>
auto parse_options(std::span<std::string_view const> args) -> Opts {
  Opts opts;
  template for (constexpr auto dm : nonstatic_data_members_of(^Opts)) {
    auto it = std::ranges::find_if(args,
      [](std::string_view arg){
        return arg.starts_with("--") && arg.substr(2) == identifier_of(dm);
      });
    if (it == args.end()) {
      //未提供选项,请使用`默认`
      continue;
    } else if (it + 1 == args.end()) {
      std::print(stderr, "Option {} is missing a value\n", *it);
      std::exit(EXIT_FAILURE);
    }
    using T = typename[:type_of(dm):];
    auto iss = std::ispanstream(it[1]);
    if (iss >> opts.[:dm:]; !iss) {
      std::print(stderr, "Failed to parse option {} into a {}\n", *it, display_string_of(^T));
      std::exit(EXIT_FAILURE);
    }
  }
  return opts;
}
struct MyOpts {
  std::string file_name = "input.txt";  //`"-file_name<string>"`选项
  int    count = 1;                     //`"-count<int>"`选项
};
int main(int argc, char *argv[]) {
  MyOpts opts = parse_options<MyOpts>(std::vector<std::string_view>(argv+1, argv+argc));
}

3.8简单元组类型

#include <meta>
template<typename... Ts> struct Tuple {
  struct storage;
  static_assert(is_type(define_class(^storage, {data_member_spec(^Ts)...})));
  storage data;
  Tuple(): data{} {}
  Tuple(Ts const& ...vs): data{ vs... } 
上一篇:图为科技与广东省北斗移动物联网产业研究院达成战略合作


下一篇:php中ajax怎么使用【小白专用24.11.12】