元编程:动态声明一个新结构

Posted

技术标签:

【中文标题】元编程:动态声明一个新结构【英文标题】:Meta programming: Declare a new struct on the fly 【发布时间】:2019-08-15 22:24:49 【问题描述】:

是否可以即时声明新类型(空结构体或没有实现的结构体)?

例如

constexpr auto make_new_type() -> ???;

using A = decltype(make_new_type());
using B = decltype(make_new_type());
using C = decltype(make_new_type());

static_assert(!std::is_same<A, B>::value, "");
static_assert(!std::is_same<B, C>::value, "");
static_assert(!std::is_same<A, C>::value, "");

“手动”解决方案是

template <class> struct Tag;

using A = Tag<struct TagA>;
using B = Tag<struct TagB>;
using C = Tag<struct TagC>;

甚至

struct A;
struct B;
struct C;

但是对于模板/元一些魔术make_new_type() 函数会很好。

现在stateful metaprogramming is ill-formed 可以实现类似的事情吗?

【问题讨论】:

为什么有人要这样做?什么是典型用例? 每个 lambda 都有一个独特的类型 :) 据我所知,它们是“只给我一个独特的类型”的成语——C++11 中唯一的一个。 相关:unconstexpr。 (从 GCC 8 起不再工作,并且那里的代码可能是格式错误的 NDR) 这个问题中的“On the fly”并不意味着在运行时。 【参考方案1】:

在 C++20 中:

using A = decltype([]); // an idiom
using B = decltype([]);
...

这是惯用代码:这就是人们在 C++20 中写“给我一个独特的类型”的方式。

在 C++11 中,最清晰最简单的方法是使用__LINE__

namespace 
  template <int> class new_type ;


using A = new_type<__LINE__>; // an idiom - pretty much
using B = new_type<__LINE__>;

匿名命名空间是最重要的部分。 不将new_type 类放在匿名命名空间中是一个严重的错误:类型在翻译单元之间将不再是唯一的。在您计划发货前 15 分钟会发生各种欢闹:)

这扩展到 C++98:

namespace 
  template <int> class new_type ;


typedef new_type<__LINE__> A; // an idiom - pretty much
typedef new_type<__LINE__> B;

另一种方法是手动链接类型,并让编译器静态验证链接是否正确完成,如果不正确,则会报错。所以它不会很脆弱(假设魔法成功了)。

类似:

namespace 
  struct base_
    using discr = std::integral_type<int, 0>;
  ;

  template <class Prev> class new_type 
    [magic here]
    using discr = std::integral_type<int, Prev::discr+1>;
  ;


using A = new_type<base_>;
using A2 = new_type<base_>;
using B = new_type<A>;
using C = new_type<B>;
using C2 = new_type<B>;

只需要一点点魔法就可以确保类型为 A2 和 C2 的行不会编译。这种魔法在 C++11 中是否可行是另一回事。

【讨论】:

C++20 中是否允许“未计算操作数中的 lambda 表达式”? 是的——这很重要。它支持一些以前根本不可能的构造,无需模板元编程。哎呀,它甚至使它们变得容易。 using discr = std::integral_type&lt;int, Prev::value+1&gt;; 中的意思是 using discr = std::integral_type&lt;int, Prev::discr+1&gt;;?还? using A = new_type&lt;base_&gt;; using A2 = new_type&lt;base_&gt;; 如何给出两种不同的类型? 它不会给出两种不同的类型,因为第二种会无法编译。实施起来有点麻烦,但这就是想法。它需要骑在标准的边缘。【参考方案2】:

你几乎可以得到你想要的语法

template <size_t>
constexpr auto make_new_type()  return [](); 

using A = decltype(make_new_type<__LINE__>());
using B = decltype(make_new_type<__LINE__>());
using C = decltype(make_new_type<__LINE__>());

这是可行的,因为每个 lambda 表达式都会产生唯一的类型。因此,对于&lt;&gt; 中的每个唯一值,您将获得一个不同的函数,该函数返回一个不同的闭包。

如果你引入一个宏,你就可以不用像 __LINE__ 这样输入了

template <size_t>
constexpr auto new_type()  return [](); 

#define make_new_type new_type<__LINE__>()

using A = decltype(make_new_type);
using B = decltype(make_new_type);
using C = decltype(make_new_type);

【讨论】:

你只依赖 __LINE__ 的唯一性(所以要注意多个 TU,或者同一行上的多个类型),所以 template &lt;size_t&gt; struct unique_tag ; 就足够了 -> #define make_new_type unique_tag&lt;__LINE__&gt;。和using A = make_new_type; @Jarod42 好点。介意我将其添加到答案中吗? 为什么不简单地using A = [](); using A = decltype([]()); 然后。 @MooingDuck: "error: lambda-expression in unevaluate context" 虽然在 C++20 之前。【参考方案3】:

我知道......它们是邪恶的......但在我看来,这是适用于旧 C 风格宏的作品

#include <type_traits>

#define  newType(x) \
struct type_##x ; \
using x = type_##x;

newType(A)
newType(B)
newType(C)

int main ()
 
   static_assert(!std::is_same<A, B>::value, "");
   static_assert(!std::is_same<B, C>::value, "");
   static_assert(!std::is_same<A, C>::value, "");
 

【讨论】:

我不认为这比 OP 已经做的更“即时”。特别是,您仍然需要将不同的标识符传递给宏。 @KonradRudolph - 我不知道...... OP 使用 = 运算符左侧的标识符,所以我认为并没有什么不同。但我不得不承认,NathanOliver 的基于 lambda 的解决方案要好得多,而且非常优雅。

以上是关于元编程:动态声明一个新结构的主要内容,如果未能解决你的问题,请参考以下文章

Groovy编译时元编程 ( 编译时元编程引入 | 声明需要编译时处理的类 | 分析 Groovy 类的 AST 语法树 )

Groovy08_MOP与元编程(方法拦截) ```

Groovy08_MOP与元编程(方法拦截) ```

干货丨DolphinDB元编程教程

GroovyMOP 元对象协议与元编程 ( Expando 动态类 | 创建动态类 | 为动态类增加字段和方法 )

Python元编程动态属性和特性