Мета-программирование: объявите новую структуру на лету - PullRequest
14 голосов
/ 25 марта 2019

Можно ли объявить новый тип (пустую структуру или структуру без реализации) на лету?

* 1003 Е.Г. *

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() функцию.

Может ли что-то подобное быть возможным сейчас, когда метапрограммирование с сохранением состояния плохо сформировано ?

Ответы [ 3 ]

22 голосов
/ 25 марта 2019

Вы можете почти получить нужный синтаксис, используя

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__>());

Это работает, поскольку каждое лямбда-выражение приводит к уникальному типу.Таким образом, для каждого уникального значения в <> вы получаете отдельную функцию, которая возвращает различное замыкание.

Если вы введете макрос, вы можете избавиться от необходимости вводить __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);
18 голосов
/ 25 марта 2019

В С ++ 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 - другая история.

2 голосов
/ 25 марта 2019

Я знаю ... они являются дистиллированным злом ... но мне кажется, что это работа для старого макроса в стиле 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, "");
 }
...