元编程:动态声明一个新结构
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<int, Prev::value+1>;
中的意思是 using discr = std::integral_type<int, Prev::discr+1>;
?还? using A = new_type<base_>; using A2 = new_type<base_>;
如何给出两种不同的类型?
它不会给出两种不同的类型,因为第二种会无法编译。实施起来有点麻烦,但这就是想法。它需要骑在标准的边缘。【参考方案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 表达式都会产生唯一的类型。因此,对于<>
中的每个唯一值,您将获得一个不同的函数,该函数返回一个不同的闭包。
如果你引入一个宏,你就可以不用像 __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 <size_t> struct unique_tag ;
就足够了 -> #define make_new_type unique_tag<__LINE__>
。和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 语法树 )