Est-il possible de déclarer un nouveau type (une structure vide ou une structure sans implémentation) à la volée?
Par exemple.
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, "");
Une solution "manuelle" est
template <class> struct Tag;
using A = Tag<struct TagA>;
using B = Tag<struct TagB>;
using C = Tag<struct TagC>;
ou même
struct A;
struct B;
struct C;
mais pour les modèles/méta, une fonction magique make_new_type()
serait bien.
Est-ce que quelque chose comme ça peut être possible maintenant que la métaprogrammation avec état est mal formée ?
En C++ 20:
using A = decltype([]{}); // an idiom
using B = decltype([]{});
...
C’est du code idiomatique: c’est ainsi qu’on écrit "donnez-moi un type unique" en C++ 20.
En C++ 11, l'approche la plus claire et la plus simple utilise __LINE__
:
namespace {
template <int> class new_type {};
}
using A = new_type<__LINE__>; // an idiom - pretty much
using B = new_type<__LINE__>;
L'espace de noms anonyme est le bit le plus important. C'est une grave erreur de ne pas mettre le new_type
classe dans l'espace de noms anonyme: les types ne seront alors plus uniques dans les unités de traduction. Toutes sortes d'hilarités s'ensuivront 15 minutes avant que vous prévoyiez d'expédier :)
Cela s'étend à C++ 98:
namespace {
template <int> class new_type {};
}
typedef new_type<__LINE__> A; // an idiom - pretty much
typedef new_type<__LINE__> B;
Une autre approche serait de chaîner manuellement les types, et de faire valider statiquement par le compilateur que le chaînage a été fait correctement, et de bombarder avec une erreur si vous ne le faites pas. Donc, ce ne serait pas fragile (en supposant que la magie fonctionne).
Quelque chose comme:
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>;
Il suffit d'un peu de magie pour s'assurer que les lignes de types A2 et C2 ne se compilent pas. Que cette magie soit possible en C++ 11 est une autre histoire.
Vous pouvez presque obtenir la syntaxe que vous souhaitez en utilisant
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__>());
Cela fonctionne car chaque expression lambda donne un type unique. Donc, pour chaque valeur unique dans <>
vous obtenez une fonction différente qui renvoie une fermeture différente.
Si vous introduisez une macro, vous pouvez vous débarrasser d'avoir à taper __LINE__
comme
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);
Je sais ... ils sont distillés mal ... mais il me semble que c'est une oeuvre pour une ancienne macro de style 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, "");
}