Home > other >  Generic way to make a template on nested typename
Generic way to make a template on nested typename

Time:07-14

Motivation:

Given a class hierarchy (and using CRTP technique with mixin tp. classes, but it is omitted here for the sake of simplicity), I would like to generically address a nested type with a known identifier, but possibly with "unknown" parent classes.

Minimal example with my first (unsuccessful) attempt:

#include <type_traits>

using namespace std;

struct A {
    using Type_x = int;
    using Type_y = char;
};

struct B {
    using Type_x = float;
    using Type_y = double;
};

template <typename T> struct C {
    using Type_x = typename T::Type_x;
    using Type_y = typename T::Type_y;
};

// not possible
// template <typename T, typename U> using Tp_type = typename T::typename U;
//
// static_assert(is_same_v<C<A>::Type_x, Tp_type<C<A>, Type_x>>);

I came up with a workaround solution, which seems to be functional, but is not too pretty. Here is an example where a derived class also introduces further nested type, but it is still handled in a quite generic way:

/// given the code above ...

struct None {};

constexpr int type_x_id = 1;
constexpr int type_y_id = 2;

template <typename T>
struct D1 : C<T> {
    using typename C<T>::Type_x;
    using typename C<T>::Type_y;

    template <int typeId>
        using Type = conditional_t<typeId == type_x_id, Type_x,
                     conditional_t<typeId == type_y_id, Type_y,
                     None>>;
};

constexpr int type_z_id = 3;

template <typename T>
struct D2 : D1<T> {
    using Type_z = long;

    template <int typeId>
        using Type = conditional_t<!is_same_v<typename D1<T>::template Type<typeId>, None>,
                                   typename D1<T>::template Type<typeId>,
                     conditional_t<typeId == type_z_id, Type_z,
                     None>>;
};

template <typename T, int typeId> using Tp_type = typename T::template Type<typeId>;

Usage/tests:

static_assert(is_same_v<D1<A>::Type_x, Tp_type<D1<A>, type_x_id>>);
static_assert(is_same_v<D1<A>::Type_y, Tp_type<D1<A>, type_y_id>>);
// static_assert(!is_same_v<D1<A>::Type_z, Tp_type<D1<A>, type_z_id>>);
static_assert(is_same_v<D2<A>::Type_x, Tp_type<D1<A>, type_x_id>>);
static_assert(is_same_v<D2<A>::Type_y, Tp_type<D1<A>, type_y_id>>);
static_assert(!is_same_v<D2<A>::Type_z, Tp_type<D1<A>, type_z_id>>);
static_assert(is_same_v<D1<A>::Type_x, Tp_type<D2<A>, type_x_id>>);
static_assert(is_same_v<D1<A>::Type_y, Tp_type<D2<A>, type_y_id>>);
// static_assert(!is_same_v<D1<A>::Type_z, Tp_type<D2<A>, type_z_id>>);
static_assert(is_same_v<D2<A>::Type_x, Tp_type<D2<A>, type_x_id>>);
static_assert(is_same_v<D2<A>::Type_y, Tp_type<D2<A>, type_y_id>>);
static_assert(is_same_v<D2<A>::Type_z, Tp_type<D2<A>, type_z_id>>);

static_assert(is_same_v<D1<B>::Type_x, Tp_type<D1<B>, type_x_id>>);
static_assert(is_same_v<D1<B>::Type_y, Tp_type<D1<B>, type_y_id>>);
// static_assert(!is_same_v<D1<B>::Type_z, Tp_type<D1<B>, type_z_id>>);
static_assert(is_same_v<D2<B>::Type_x, Tp_type<D1<B>, type_x_id>>);
static_assert(is_same_v<D2<B>::Type_y, Tp_type<D1<B>, type_y_id>>);
static_assert(!is_same_v<D2<B>::Type_z, Tp_type<D1<B>, type_z_id>>);
static_assert(is_same_v<D1<B>::Type_x, Tp_type<D2<B>, type_x_id>>);
static_assert(is_same_v<D1<B>::Type_y, Tp_type<D2<B>, type_y_id>>);
// static_assert(!is_same_v<D1<B>::Type_z, Tp_type<D2<B>, type_z_id>>);
static_assert(is_same_v<D2<B>::Type_x, Tp_type<D2<B>, type_x_id>>);
static_assert(is_same_v<D2<B>::Type_y, Tp_type<D2<B>, type_y_id>>);
static_assert(is_same_v<D2<B>::Type_z, Tp_type<D2<B>, type_z_id>>);

static_assert(!is_same_v<D1<A>::Type_x, Tp_type<D1<B>, type_x_id>>);
static_assert(!is_same_v<D1<A>::Type_y, Tp_type<D1<B>, type_y_id>>);
// static_assert(!is_same_v<D1<A>::Type_z, Tp_type<D1<B>, type_z_id>>);
static_assert(!is_same_v<D2<A>::Type_x, Tp_type<D1<B>, type_x_id>>);
static_assert(!is_same_v<D2<A>::Type_y, Tp_type<D1<B>, type_y_id>>);
static_assert(!is_same_v<D2<A>::Type_z, Tp_type<D1<B>, type_z_id>>);
static_assert(!is_same_v<D1<A>::Type_x, Tp_type<D2<B>, type_x_id>>);
static_assert(!is_same_v<D1<A>::Type_y, Tp_type<D2<B>, type_y_id>>);
// static_assert(!is_same_v<D1<A>::Type_z, Tp_type<D2<B>, type_z_id>>);
static_assert(!is_same_v<D2<A>::Type_x, Tp_type<D2<B>, type_x_id>>);
static_assert(!is_same_v<D2<A>::Type_y, Tp_type<D2<B>, type_y_id>>);
static_assert(is_same_v<D2<A>::Type_z, Tp_type<D2<B>, type_z_id>>);

Is there a better solution? For example, a one that would not require to define Type every time a new nested type identifier comes along.

CodePudding user response:

You can't pass names around and look up types based on them without writing entire libraries or using compile-time reflection (which looks like at this point).

But you want to be able to pass around identifiers at compile time. Your int solution is a bit awkward; I'd suggest passing around templates that do the lookup:

template<class T>
using Type_x_t = typename T::Type_x;
template<class T>
using Type_y_t = typename T::Type_y;

etc

Then you can do:

template <typename T, template<class...> class U_t>
using Tp_type_t = U_t<T>;

static_assert(
  std::is_same_v<D1<A>::Type_x, Tp_type_t<D1<A>, Type_x_t>);
);

or:

static_assert(
  std::is_same_v<D1<A>::Type_x, Type_x_t<D1<A>>);
);

We pass around template Foo_t<> that maps T to T::Foo, then that acts as a "name" for a subtype.

  • Related