Мета-программирование: объявите новую структуру на лету
Можно ли объявить новый тип (пустую структуру или структуру без реализации) на лету?
Например
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()
.
Может ли что-то подобное быть возможным сейчас, когда метапрограммирование с состоянием плохо сформировано?
Ответы
Ответ 1
Вы можете почти получить синтаксис, который вы хотите использовать
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__
макрос, вы можете избавиться от необходимости вводить __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);
Ответ 2
В С++ 20:
using A = decltype([]{}); // an idiom
using B = decltype([]{});
...
Это идиоматический код: так пишут "дай мне уникальный тип" в С++ 20.
В С++ 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 минут до того, как вы планируете отправить :)
Это распространяется на С++ 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 не компилируются. Возможна ли эта магия в С++ 11 - другая история.
Ответ 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, "");
}