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

kay*_*kay 14 c++ templates metaprogramming stateful compile-time-constant

是否可以即时声明一个新类型(一个空的struct或没有实现的struct)?

例如

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, "");
Run Code Online (Sandbox Code Playgroud)

一个“手动”解决方案是

template <class> struct Tag;

using A = Tag<struct TagA>;
using B = Tag<struct TagB>;
using C = Tag<struct TagC>;
Run Code Online (Sandbox Code Playgroud)

甚至

struct A;
struct B;
struct C;
Run Code Online (Sandbox Code Playgroud)

但是对于模板/ meta,一些魔术make_new_type()功能会很好。

有状态的元编程格式不正确,这样的事情是否可能呢?

Nat*_*ica 22

您几乎可以得到想要使用的语法

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__>());
Run Code Online (Sandbox Code Playgroud)

这是可行的,因为每个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);
Run Code Online (Sandbox Code Playgroud)

  • 您只依赖于__LINE__的唯一性(因此要注意多个TU或同一行上的几种类型),所以template &lt;size_t&gt; struct unique_tag {};就足够了-&gt;`#define make_new_type unique_tag &lt;__ LINE __&gt;` 。和`using A = make_new_type;` (7认同)
  • @MooingDuck:*“错误:未评估上下文中的 lambda 表达式”* 在 C++20 之前。 (2认同)

Rei*_*ica 18

在C ++ 20中:

using A = decltype([]{}); // an idiom
using B = decltype([]{});
...
Run Code Online (Sandbox Code Playgroud)

这是惯用的代码:这就是用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__>;
Run Code Online (Sandbox Code Playgroud)

匿名名称空间是最重要的部分。不将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;
Run Code Online (Sandbox Code Playgroud)

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

就像是:

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>;
Run Code Online (Sandbox Code Playgroud)

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