Germán Diago
Germán Diago

Reputation: 7663

Metaprogramming tricks: how to simplify implementation of two metafunctions

I am writing some program to call some APIs automatically through code generation.
In some cases I need to convert from a type Source, to a type Target, but these types come decorated with pointers, const, etc. So what I need to do is to remove all decorations such as pointer, const, array, etc, get the plain type to map it to another type, and later, apply the decorations back into the new type.

The implementation has lots of template specializations. Questions after the code. I cannot use constexpr metaprogramming because I need to make it work with VS2013.

template <class T>
struct TypeIs {
    using type = T;
};

template <class T>
struct GetPlainType : TypeIs<typename std::decay<T>::type> {};

template <class T>
struct GetPlainType<T&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const &> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T &&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const &&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T*> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const *> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T[]> : TypeIs<typename GetPlainType<T>::type> {};


template <class T>
struct GetPlainType<T const[]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T, std::size_t I>
struct GetPlainType<T[I]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T, std::size_t I>
struct GetPlainType<T const [I]> : TypeIs<typename GetPlainType<T>::type> {};


template <class T>
using GetPlainType_t = typename GetPlainType<T>::type;


template <class Decorated, class Plain>
struct CopyDecorations : TypeIs<Plain> {};


template <class T, class Plain>
struct CopyDecorations<T const, Plain> :
    TypeIs<typename CopyDecorations<T, Plain const>::type> {};


template <class T, class Plain>
struct CopyDecorations<T *, Plain> :
    TypeIs<typename CopyDecorations<T, Plain *>::type> {};

template <class T, class Plain>
struct CopyDecorations<T const *, Plain> :
    TypeIs<typename CopyDecorations<T, Plain const *>::type> {};


template <class T, class Plain>
struct CopyDecorations<T &, Plain> :
    TypeIs<typename CopyDecorations<T, Plain &>::type> {};

template <class T, class Plain>
struct CopyDecorations<T const &, Plain> :
    TypeIs<typename CopyDecorations<T, Plain const &>::type> {};

template <class T, class Plain>
struct CopyDecorations<T  &&, Plain> :
    TypeIs<typename CopyDecorations<T, Plain &&>::type> {};


template <class T, class Plain>
struct CopyDecorations<T  const &&, Plain> :
    TypeIs<typename CopyDecorations<T, Plain const &&>::type> {};


template <class T, class Plain>
struct CopyDecorations<T[], Plain> :
     TypeIs<typename CopyDecorations<T, Plain[]>::type> {};

template <class T, class Plain>
struct CopyDecorations<T const [], Plain> :
TypeIs<typename CopyDecorations<T, Plain const []>::type> {};


template <class T, class Plain, std::size_t I>
struct CopyDecorations<T [I], Plain> :
     TypeIs<typename CopyDecorations<T, Plain[I]>::type> {};

template <class T, class Plain, std::size_t I>
struct CopyDecorations<T const [I], Plain> :
     TypeIs<typename CopyDecorations<T, Plain const [I]>::type> {};


template <class Decorated, class Plain>
using CopyDecorations_t = typename CopyDecorations<Decorated, Plain>::type;


int main()
{
    static_assert(std::is_same<GetPlainType_t<int>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int *>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int **>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int * &>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int ** &>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const * []>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const **[][3][5]>, int>{}, "");


    static_assert(std::is_same<CopyDecorations_t<int, double>, double>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int const, double>, double const>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int *, double>, double *>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int **, double>, double **>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int[], double>, double[]>{}, "");
    static_assert(std::is_same<CopyDecorations_t<int[3], double>, double[3]>{}, "");


    //******************THE TESTS BELOW DO NOT WORK
    //static_assert(std::is_same<CopyDecorations_t<int[][3], double>, double[][3]>{}, "");
    //static_assert(std::is_same<CopyDecorations_t<int * &, double>, double * &>{}, "");
    // static_assert
    //     (
    //std::is_same<CopyDecorations_t<int const * [], double>,
    //      double const * []>{}, "");
    // static_assert
    //     (std::is_same<CopyDecorations_t<int const **[][3][5], double>,
    //      double const **[][3][5]>{}, "");
}

Questions:

  1. Can I simplify the implementation?
  2. The tests that fail (see main function), how can I fix them?
  3. In which cases (ignoring volatile and pointers to members, pointers to functions, and functions). can you think my implementation will fail?

Upvotes: 9

Views: 937

Answers (4)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275300

So you can do this with a function that pattern matches and does one step of a transcription, like so:

template<class In, class Out>
struct types {
  using type=types;
  using in=In;
  using out=Out;
};

// transcribe cv:
template<class In, class Out>
types<In, const Out> transcribe( types<const In, Out> ) { return {}; }
template<class In, class Out>
types<In, volatile Out> transcribe( types<volatile In, Out> ) { return {}; }
template<class In, class Out>
types<In, const volatile Out> transcribe( types<const volatile In, Out> ) { return {}; }

// references and pointers:
template<class In, class Out>
types<In, Out*> transcribe( types<In*, Out> ) { return {}; }
template<class In, class Out>
types<In, Out&> transcribe( types<In&, Out> ) { return {}; }
template<class In, class Out>
types<In, Out&&> transcribe( types<In&&, Out> ) { return {}; }

// arrays
template<class In, class Out>
types<In, Out[]> transcribe( types<In[], Out> ) { return {}; }
template<class In, class Out, std::size_t N>
types<In, Out[N]> transcribe( types<In[N], Out> ) { return {}; }

// return type of a function
template<class In, class...In_Args, class Out>
types<In, Out(In_Args...)> transcribe( types<In(In_Args...), Out> ) { return {}; }
// return type of a function
template<class In, class...In_Args, class Out>
types<In, Out(*)(In_Args...)> transcribe( types<In(*)(In_Args...), Out> ) { return {}; }

// default case
template<class X>
X transcribe( X ) { return {}; }

overload rules do the right thing. The only nasty thing is the const volatile case.

Once we have the above, we can turn it into a trait:

template<class In, class Out>
struct transcribe_one:
  decltype(transcribe( types<In,Out>{} ))
{};

easily. Note that transcribe need never be called.

Aliases make this easier to use:

template<class T>
using strip_one=typename transcribe_one<T, int>::in;
template<class T>
using can_strip=std::integral_constant<bool, !std::is_same<T, strip_one<T>>{}>;
template<class T, class U>
using typescribe_one=typename transcribe_one<T, U>::out;

and also express "is there anything to strip?".

This only moves over one type adornment from the left to the right. To move them all, we just do this:

template<class In, class Out, class=void>
struct transcribe_all:types<In, Out> {};

template<class T>
using strip=typename transcribe_all<T, int>::in;
template<class T, class U>
using typescribe=typename transcribe_all<T, U>::out;

template<class In, class Out>
struct transcribe_all<In, Out, std::enable_if_t<
  can_strip<In>{}
>> :
types<
  strip< strip_one< In > >, // must strip on strip_one, trust me
  typescribe_one<
    In,
    typescribe< strip_one<In>, Out >
  >
>
{};

which, when you cannot strip, does nothing.

When you can strip, it strips one off the In type, transcribes the remainder onto Out, then does a one-step transcription onto the result of that.

This gives you two aliases:

template<class T>
using strip=// ...
template<class T, class U>
using typescribe=// ...

the first one takes a type T and strips it down to the "raw" type at the bottom.

The second takes a type T and moves all of its decorations over to U.

template<template<class...>class M, class U>
using under_map = typescribe< U, M<strip<U>> >;

which strips off the type adornments, applies M, then reapplies them.

Note that the most "user friendly" tools are strip<T> and typescribe<In, Out>. The first removes all decorators and gets the "underlying" type. The second copies decorators from one type to another. Using transcribe_all or transcribe_one or strip_one etc will probably lead to confusion, they are implementation details.

Simply write your type map as a template and pass it to under_map.

live example.

The only C++14 feature used is std::enable_if_t<?>, which can be replaced with typename std::enable_if<?>::type on C++11.

MSVC might have problems with the decltype stuff, as its support of decltype in SFINAE is abysmal.

Upvotes: 3

Germ&#225;n Diago
Germ&#225;n Diago

Reputation: 7663

This is my solution. It works for my needs (no volatile).

template <class T>
struct TypeIs {
    using type = T;
};

template <class T>
struct GetPlainType : TypeIs<typename std::decay<T>::type> {};

template <class T>
struct GetPlainType<T const> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const &> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T &&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const &&> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T*> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const *> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T[]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
struct GetPlainType<T const[]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T, std::size_t I>
struct GetPlainType<T[I]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T, std::size_t I>
struct GetPlainType<T const [I]> : TypeIs<typename GetPlainType<T>::type> {};

template <class T>
using GetPlainType_t = typename GetPlainType<T>::type;

namespace detail {

//Qualifiers
struct ConstQual {};

//Category
struct ValueCat {};

template <std::size_t I = 0>
struct ArrayCat  : std::integral_constant<std::size_t, I> {};

struct PointerCat {};
struct LValueReferenceCat {};
struct RValueReferenceCat {};


template <class Cat, class...Quals>
struct Decoration {
    using Category = Cat;
    using Qualifiers = std::tuple<Quals...>;
};

template <class Cat, class...Quals>
using DecorationCategory_t = typename Decoration<Cat, Quals...>::type;


template <class T>
struct SaveDecorations : TypeIs<brigand::list<Decoration<ValueCat>>> {};

template <class T>
struct SaveDecorations<T const> : TypeIs<brigand::list<Decoration<ValueCat, ConstQual>>> {};


template <class T>
struct SaveDecorations<T *> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<PointerCat>>>> {};


template <class T>
struct SaveDecorations<T * const> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<PointerCat, ConstQual>>>> {};

template <class T>
struct SaveDecorations<T &> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<LValueReferenceCat>>>> {};

template <class T>
struct SaveDecorations<T &&> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<RValueReferenceCat>>>> {};


template <class T>
struct SaveDecorations<T []> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<ArrayCat<>>>
                                 >> {};

template <class T>
struct SaveDecorations<T const []> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T const>::type,
        brigand::list<Decoration<ArrayCat<>>>
                                 >> {};

template <class T, std::size_t N>
struct SaveDecorations<T [N]> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T>::type,
        brigand::list<Decoration<ArrayCat<N>>>
        >> {};


template <class T, std::size_t N>
struct SaveDecorations<T const [N]> :
        TypeIs<
    brigand::append<
    typename SaveDecorations<T const>::type,
        brigand::list<Decoration<ArrayCat<N>>>
        >> {};


template <class State, class Elem>
struct AddDecoration : TypeIs<State> {};


template <class State>
struct AddDecoration<State, Decoration<ValueCat, ConstQual>> : TypeIs<State const> {};

template <class State>
struct AddDecoration<State, Decoration<PointerCat>> : TypeIs<State *> {};

template <class State>
struct AddDecoration<State, Decoration<PointerCat, ConstQual>> :
        TypeIs<State * const> {};

template <class State>
struct AddDecoration<State, Decoration<LValueReferenceCat>> : TypeIs<State &> {};

template <class State>
struct AddDecoration<State, Decoration<RValueReferenceCat>> : TypeIs<State &&> {};

template <class State>
struct AddDecoration<State, Decoration<ArrayCat<>>> : TypeIs<State[]> {};

template <class State, std::size_t I>
struct AddDecoration<State, Decoration<ArrayCat<I>>> : TypeIs<State[I]> {};


template <class T, class DecorationsList>
struct ApplyDecorations :
        TypeIs<brigand::fold<DecorationsList, T,
                             AddDecoration<brigand::_state,
                                           brigand::_element>>> {};
}


template <class T>
using SaveDecorations_t = typename detail::SaveDecorations<T>::type;

template <class T, class TList>
using ApplyDecorations_t = typename detail::ApplyDecorations<T, TList>::type;


int main()
{
    static_assert(std::is_same<GetPlainType_t<int>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int *>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int **>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int * &>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int ** &>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const * []>, int>{}, "");
    static_assert(std::is_same<GetPlainType_t<int const **[][3][5]>, int>{}, "");

    using Decorations = SaveDecorations_t<int>;
    using shouldBeFloat = ApplyDecorations_t<float, Decorations>;
    static_assert(std::is_same<float, shouldBeFloat>{}, "");

    using Decorations2 = SaveDecorations_t<int const>;
    using shouldBeConst = ApplyDecorations_t<float, Decorations2>;
    static_assert(std::is_same<shouldBeConst, float const>{}, "");

    using Decorations3 = SaveDecorations_t<int const *>;
    using shouldPointerToConst = ApplyDecorations_t<float, Decorations3>;
    static_assert(std::is_same<shouldPointerToConst, float const *>{}, "");

    using Decorations4 = SaveDecorations_t<int const * const>;
    using shouldConstPointerToConst = ApplyDecorations_t<float, Decorations4>;
    static_assert(std::is_same<shouldConstPointerToConst, float const * const>{}, "");


    using Decorations5 = SaveDecorations_t<int const * const &>;
    using shouldBeLValRefToConstPointerToConst = ApplyDecorations_t<float, Decorations5>;
    static_assert(std::is_same<shouldBeLValRefToConstPointerToConst, float const * const &>{}, "");

    using Decorations6 = SaveDecorations_t<int * const ** const &>;
    using Res = ApplyDecorations_t<float, Decorations6>;
    static_assert(std::is_same<Res, float * const ** const &>{}, "");

    using Decorations7 = SaveDecorations_t<int * const>;
    using Res2 = ApplyDecorations_t<float, Decorations7>;
    static_assert(std::is_same<Res2, float * const>{}, "");

    //Arrays tests
    using Decorations8 = SaveDecorations_t<int const * const * const []>;
    using Res3 = ApplyDecorations_t<float, Decorations8>;
    static_assert(std::is_same<Res3, float const * const * const []>{}, "");


    using Decorations9 = SaveDecorations_t<int const * const * [3]>;
    using Res4 = ApplyDecorations_t<float, Decorations9>;
    static_assert(std::is_same<Res4, float const * const * [3]>{}, "");

    //Multidimensional arrays
    using Decorations10 = SaveDecorations_t<int const * const * [3][5]>;
    using Res5 = ApplyDecorations_t<float, Decorations10>;
    static_assert(std::is_same<Res5, float const * const * [3][5]>{}, "");

    using Decorations11 = SaveDecorations_t<int const * const * [][3][5]>;
    using Res6 = ApplyDecorations_t<float, Decorations11>;
    static_assert(std::is_same<Res6, float const * const * [][3][5]>{}, "");
}

Upvotes: -2

skypjack
skypjack

Reputation: 50540

I found this question one of the most interesting one about C++ metaprogramming on SO indeed.
I enjoyed trying to find a proper solution. Thank you. :-)


It follows a minimal, working example.
It is not complete, but it gives an idea of a possible approach to be used to do that.
The function f (ok, you can choose a better name in your code) accepts two template parameters: the type to be cleaned and the one to be decorated.
It returns a template type (types) that introduces two using declarations, basic and decorated, with the first template parameter cleaned up as basic and the second one decorated as decorated.
It does all at once (cleaning up and decoration). You can still use only the first parameter, in this case decorated is defaulted to a decorated char type.

Here is the full code:

#include<type_traits>
#include<cstddef>

static constexpr std::size_t N = 42;

template<std::size_t N>
struct choice: choice<N-1> {};

template<>
struct choice<0> {};

template<typename T, typename U>
struct types {
    using basic = T;
    using decorated = U;
};

template<typename T, typename U>
constexpr auto
f(choice<0>) { return types<T, U>{}; }

template<typename T, typename U,
    typename = std::enable_if_t<std::is_pointer<T>::value>>
constexpr auto f(choice<1>) {
    auto t = f<std::remove_pointer_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_pointer_t<D>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_lvalue_reference<T>::value>>
constexpr auto f(choice<2>) {
    auto t = f<std::remove_reference_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_lvalue_reference_t<D>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_rvalue_reference<T>::value>>
constexpr auto f(choice<3>) {
    auto t = f<std::remove_reference_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_rvalue_reference_t<D>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_array<T>::value>>
constexpr auto f(choice<4>) {
    auto t = f<std::remove_extent_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::conditional_t<(0==std::extent<T>::value), D[], D[std::extent<T>::value]>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_const<T>::value>>
constexpr auto f(choice<5>) {
    auto t = f<std::remove_const_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_const_t<D>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_volatile<T>::value>>
constexpr auto f(choice<6>) {
    auto t = f<std::remove_volatile_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_volatile_t<D>>{};
}

template<typename T, typename U = char>
constexpr auto f() {
    return f<T, U>(choice<N>{});
}

int main() {
    // something complex to show that it seems to work
    static_assert(std::is_same<
        decltype(f<const int ** const &&, char>()),
        types<int, const char ** const &&>
    >::value, "!");

    // some of the OP's examples (the most interesting)
    static_assert(std::is_same<decltype(f<int, int>()), types<int, int>>::value, "!");
    static_assert(std::is_same<decltype(f<int const, int>()), types<int, int const>>::value, "!");
    static_assert(std::is_same<decltype(f<int *, int>()), types<int, int *>>::value, "!");
    static_assert(std::is_same<decltype(f<int **, double>()), types<int, double **>>::value, "!");
    static_assert(std::is_same<decltype(f<int *&, int>()), types<int, int *&>>::value, "!");
    static_assert(std::is_same<decltype(f<int **&, float>()), types<int, float **&>>::value, "!");
    static_assert(std::is_same<decltype(f<int [3], char>()), types<int, char [3]>>::value, "!");
    static_assert(std::is_same<decltype(f<int [], int>()), types<int, int []>>::value, "!");
    static_assert(std::is_same<decltype(f<int [][3], double>()), types<int, double [][3]>>::value, "!");
    static_assert(std::is_same<decltype(f<int const **[][3][5], int>()), types<int, int const **[][3][5]>>::value, "!");

    // of course, you don't need to provide the second type if you don't need it
    // in this case, types::decorated is defaulted to a decorated char type
    f<int const **[][3][5]>();
}

Set apart the fact that it won't compile without the constexprs because of the static_asserts, you can freely remove them and use the function at runtime.

Actually, it could be turned maybe to a definition-less solution, providing the right return types to the declarations and using a bunch of decltypes, but I suspect that it would be far from being readable.

EDIT

As mentioned by the OP, he doesn't want (or at least, he cannot use) constexprs.
It follows a slightly different solution, still based on the previous one.
The basic idea is to use f as an unevaluated operand with decltype.
Here is the full code:

#include<type_traits>
#include<cstddef>

static const std::size_t N = 42;

template<std::size_t N>
struct choice: choice<N-1> {};

template<>
struct choice<0> {};

template<typename T, typename U>
struct types {
    using basic = T;
    using decorated = U;
};

template<typename T, typename U>
auto f(choice<0>) { return types<T, U>{}; }

template<typename T, typename U,
    typename = std::enable_if_t<std::is_pointer<T>::value>>
auto f(choice<1>) {
    auto t = f<std::remove_pointer_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_pointer_t<D>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_lvalue_reference<T>::value>>
auto f(choice<2>) {
    auto t = f<std::remove_reference_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_lvalue_reference_t<D>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_rvalue_reference<T>::value>>
auto f(choice<3>) {
    auto t = f<std::remove_reference_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_rvalue_reference_t<D>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_array<T>::value>>
auto f(choice<4>) {
    auto t = f<std::remove_extent_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::conditional_t<(0==std::extent<T>::value), D[], D[std::extent<T>::value]>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_const<T>::value>>
auto f(choice<5>) {
    auto t = f<std::remove_const_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_const_t<D>>{};
}

template<typename T, typename U,
    typename = std::enable_if_t<std::is_volatile<T>::value>>
auto f(choice<6>) {
    auto t = f<std::remove_volatile_t<T>, U>(choice<N>{});
    using B = typename decltype(t)::basic;
    using D = typename decltype(t)::decorated;
    return types<B, std::add_volatile_t<D>>{};
}

template<typename T, typename U>
auto f() {
    return f<T, U>(choice<N>{});
}

template<typename T, typename U = char>
using my_type = decltype(f<T, U>());

template<typename T, typename U = char>
using my_type_basic_t = typename decltype(f<T, U>())::basic;

template<typename T, typename U = char>
using my_type_decorated_t = typename decltype(f<T, U>())::decorated;

int main() {
    int i = 42;
    my_type_decorated_t<char *, int> ptr = &i;
    // of course, it can still be used in a constant expression if needed
    // constexpr my_type_decorated_t<char *, int> ptr = nullptr;

}

Upvotes: 5

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275300

I like doing type metaprogramming with tags.

enum type_quals {
  none_qualified = 0,
  const_qualified = 1,
  volatile_qualified = 2,
  lreference_qualified = 4,
  rreference_qualified = 8,
  pointer_qualified = 16,
  all_qualified = 31,
};

template<type_quals qs, class inside=void>
struct tq_t {
  constexpr tq() {};
  constexpr explicit operator bool() const { return qs!=none_qualified; }
};

template<type_quals q>
tq_t<q> tq{};

template<type_quals a, type_quals b>
constexpr
tq_t< type_quals(unsigned(a)|unsigned(b)) >
operator|( tq_t<a>, tq_t<b> ) { return {}; }

template<type_quals a, type_quals b>
constexpr
tq_t< type_quals(a&b) >
operator&( tq_t<a>, ta_t<b> ) { return {}; }

template<class T>
struct tag_t {
  constexpr tag_t() {};
  using type=T;
};

template<class T>
tag_t<T> tag{};

template<class T>
constexpr
tag_t<const T>
operator+( tag_t<T>, tq_t<const_qualified> ) { return {}; }

template<class T>
constexpr
tag_t<volatile T>
operator+( tag_t<T>, tq_t<volatile_qualified> ) { return {}; }

template<class T>
constexpr
tag_t<T&>
operator+( tag_t<T>, tq_t<lreference_qualified> ) { return {}; }

template<class T>
constexpr
tag_t<T&&>
operator+( tag_t<T>, tq_t<rreference_qualified> ) { return {}; }

template<class T>
constexpr
tag_t<T>
operator+( tag_t<T>, tq_t<none_qualified> ) { return {}; }

template<class T, type_quals qs>
constexpr
auto
operator+( tag_t<T> t, tq_t<qs> q ) {
  return t
    +(q&tq<const_qualified>)
    +(q&tq<volatile_qualified>)
    +(q&tq<lreference_qualified>)
    +(q&tq<rreference_qualified>)
  ;
}

template<class T, type_quals qs>
constexpr
auto
operator+( tq_t<qs> q, tag_t<T> t ) {
  return t+q;
}

Now once you have a tq and a tag, you can do addition to add the types back onto the tag.

Pointers require nesting, so are a different kind of tq, but the pattern is somewhat similar. The above doesn't support nesting yet.

You now need code to detect the tqs in a tag and remove them. Maybe add a tq&tag operator that returns the tq on the tag. Then add tag-tq to strip the matching tq from the tag.

Your code ends up looking like:

auto decorations = tag<T>&tq<all_qualified>;
auto raw = tag<T>-decorations;
// use raw::type here for the undecorated type
// produce type R
auto redecorated = tag<R>+decorations;
return redecorated;

Now, all this really does is move the annoying type stuff out of the line of the business logic. But it does so in a pretty way.

Upvotes: 1

Related Questions