9

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?
skypjack
  • 49,335
  • 19
  • 95
  • 187
Germán Diago
  • 7,473
  • 1
  • 36
  • 59
  • I'm a year away from my last C++ work, but my first thought would be to look at `std::decay`, `std::remove_reference`, etc – Mark K Cowan Aug 25 '16 at 11:23
  • std::decay already removes const-volatile and refs. Most of the specializations are not needed. – DeiDei Aug 25 '16 at 11:25
  • 1
    [CodeReview.SE](http://codereview.stackexchange.com/) might be a better fit for this kind of question – tkausl Aug 25 '16 at 11:26
  • @tkausl *ish*. "Can I simplify the implementation?", that's a fine question for Code Review. "Why are my tests failing?", that's definitely not. – Kaz Aug 25 '16 at 11:28
  • @DeiDei that will just remove the top level. I need to recurse. – Germán Diago Aug 26 '16 at 03:14
  • I would change the title indeed, the question is about _how to transfer qualifiers, pointers, etc. from one type to another_. Am I wrong? That title is fine for stackexchange, but it doesn't describe correctly the question on SO. – skypjack Aug 26 '16 at 09:32

4 Answers4

5

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;

}
skypjack
  • 49,335
  • 19
  • 95
  • 187
  • I voted you up because the solution is nice and it will work with constexpr support. Unfortunately, I am stuck with VS2013 so I need pure template metaprogramming. It is also my bad indeed, I will edit the question contents. – Germán Diago Aug 26 '16 at 07:55
  • as I understand this does not require constexpr because functions are never evaluated? I mean, I can just remove constexpr? I do not have access at this moment to a windows machine but could try next monday if that will work. – Germán Diago Aug 26 '16 at 08:22
  • @GermánDiago You can use `f` as an unevaluated operand and that's all. [Here](https://godbolt.org/g/wnkddl) is an example: no more `constexpr`s. I'm adding the same example to the answer, for further references. – skypjack Aug 26 '16 at 08:30
  • I will mark it as the accepted answer. Just for the record, I have a pure metaprogramming solution already working here, hehe. – Germán Diago Aug 26 '16 at 10:02
  • when totally refined I will post it, can I after marking as solved? – Germán Diago Aug 26 '16 at 10:18
  • @GermánDiago You can post it as an answer, even if there exists one already accepted. – skypjack Aug 26 '16 at 10:26
3

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Just fixed a typo at the end of the answer. Let me look over it before any other comment. :-) – skypjack Aug 26 '16 at 20:41
  • Ok. It's based almost on the same idea of the accepted answer and you encountered the same _problems_: `const` and `volatile`. It took me a while to workaround them indeed. Actually `choice` is not required, you are right, but I find a solution that uses _sfinae'd_ functions and forces the overloading using `choice` a bit easier to read. It's more compact in terms of lines of code and maybe it's easier to reason about. Anyway +1 for this one is a far more elegant and clean solution. I must admit that. Chapeau. Thank you very much. – skypjack Aug 26 '16 at 20:51
  • I am not sure abt a detail: seems that things are stripped right to left. But later will be applied left to right? I had this problem with my solution at first. So float const * would give me in the transcriptio n float * const. – Germán Diago Aug 29 '16 at 14:26
  • @GermánDiago The slightly convoluted `types< strip< strip_one< In > >, typescribe_one< In, typescribe< strip_one, Out > > >` attempts to ensure the stripping and moving goes in the same direction. I had a similar problem (where the moved features applied in reverse order), and I believe I fixed it. Notice we strip once, transcribe that over, then transcribe the one we stripped on top of that. – Yakk - Adam Nevraumont Aug 29 '16 at 14:32
  • Ok, that should work out then. Yes, that "made my day" and made me even pull brigand for more elaborate solutions, haha. Thanks. – Germán Diago Aug 29 '16 at 15:05
  • Testing transcribe_one I see that const is not transferred for this case: static_assert(std::is_same::out, float &>{}, ""); I had this problem before also. You need to overload T, T const, T const &, T &, T const &&. Same for arrays: T const [], T const [N] and more. Do not ask me why, I just know it works like that. This is the very reason why my solution has overloads for T const [], etc., because it did not work when I tested it. Strangely, when you do T const []. The accurate way to model this is (even if not following my solution)... – Germán Diago Aug 29 '16 at 15:29
  • ...to consider pointers and values as const/volatile overloadable. for example, T const & is an lvalue reference to const T, but you must overload it for it it be taken into account as a different specialization. However, T const & is an overload. So what I did is: overload T const & and just add a Decoration as "LValue reference" and in the recursion pass T const instead of plain const. I always put const next to a value or pointer, they are the only entities that have it. – Germán Diago Aug 29 '16 at 15:32
  • This solution, hence, is not correct. I moved the flag many times already but if I do not move it again I will mislead people to think this is the correct solution, so I think I should. Anyway, your insights have been very useful to me and I cannot vote up enough the solution. It is just a matter of "correct" for my question. – Germán Diago Aug 29 '16 at 15:32
  • @GermánDiago You called `transcribe_one` which only moves over one type decorator. Use `typescribe` to get `float const&`, or use `transcribe_all::out`. [live example](http://coliru.stacked-crooked.com/a/a4fff824198fc62d). transcribe_one moves over only the reference, because you asked it to move over *one* decorator. That is what the `_one` things do. The ones without `_one` move over everything, and should do it in the proper order. (`_one` will bundle `const` and `volatile` and `const volatile` as one step, because I was lazy). – Yakk - Adam Nevraumont Aug 29 '16 at 15:36
  • Oh, I see the problem, this models things moving one by one only. It works different to my solution that is why I got confused. Thanks! In your solution it is two recursions. – Germán Diago Aug 29 '16 at 15:41
  • Ok, works like a charm: tag_t d = tag>; (void)d; tag_t e = tag>; (void)e; tag_t f = tag>; (void)f; – Germán Diago Aug 29 '16 at 15:47
1

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • This does not allow sized and unsized arrays and nesting. – Germán Diago Aug 25 '16 at 13:32
  • I did not mention this either but I have to stick to VS2013 so I cannot make use of constexpr metaprogramming. But looks nice. – Germán Diago Aug 25 '16 at 13:34
  • @germ arrays would work like pointers. Sized arrays can carry size information in another argument to `tq_t`. But yes, with a partial C++11 implementation this approach probably won't work. – Yakk - Adam Nevraumont Aug 25 '16 at 13:42
  • @germ the general pattern -- stuff qualifiers in a second type to graft on again -- can work. Just not with `+` and the nice syntax. – Yakk - Adam Nevraumont Aug 25 '16 at 13:44
  • @Yakk For you are far more skilled than me, I'd like you to review my answer _if possible_. This question was really interesting from my point of view and I spent a while searching a proper solution, so any constructive critique would be appreciated. Thank you in advance in any case. – skypjack Aug 26 '16 at 08:52
  • @skypjack I demonstrated that `choice` isn't really needed in http://stackoverflow.com/a/39174280/1774667 -- with careful choice, the default overload order works just fine. – Yakk - Adam Nevraumont Aug 26 '16 at 20:35
-2

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]>{}, "");
}
Germán Diago
  • 7,473
  • 1
  • 36
  • 59
  • It's fine, but it's far more complex. Moreover, you have to explicitly specify all the _decorators_ (as an example, `T []` and `T const []` are treated separately), so it' more verbose and error prone. Anyway, if it works for you, it's fine. There doesn't exist the perfect solution. :-) – skypjack Aug 29 '16 at 10:00
  • Actually I could not take a deep look. I just know that: const can only go with pointers and types actually. The arrays are a hell, with the overloading approach I could not return T[], for example, which messed up things further. So I had to come back to pure template metaprogramming. This solution, at least works for me :) I do not claim it to be the best. Also, the return type for a function for folding/accumulating was not clear at all for me, and I cannot use constexpr. – Germán Diago Aug 29 '16 at 10:05
  • Would be nice if people explained the downvotes for a working solution that works with VS2013 and arrays included. – Germán Diago Aug 29 '16 at 12:33
  • I suspect it's due to the fact that you weren't voting the _best_ solution as for the _best_ definition of SO, but your own for some other reasons (moving the flag after a week). There are answers that are more elegant and easy to maintain and work also with VS2013 and arrays (as shown in the examples), so people vote them for they are more useful for future readers. Your answer is fine for your case and you should probably use it, but SO is something different and has its rules and criteria. Upvote and downvote are useful to promote good answers and questions, you shouldn't blame downvoters. – skypjack Aug 29 '16 at 14:16
  • Moreover, answers that start with a mention for another user are not welcome usually. Answers are for everybody. :-) – skypjack Aug 29 '16 at 14:18
  • Oh my mistake. This just came from the fact that you explicitely asked me for my solution. Sorry for that. Will remove the mention. – Germán Diago Aug 29 '16 at 14:20
  • Moving the flag was just an accident bc I accepted last time the other solution. Was not on purpose. I am not that partial. I try to be fair :) – Germán Diago Aug 29 '16 at 14:20
  • Well, today you moved it on three different answers: from mine, to your, to Yakk's. What's the right one? :-D – skypjack Aug 29 '16 at 14:22
  • Lol. I messed up something. We have several solutions. I think the one closest to working with my restrictions and more complete is transcribe solution. I do not mean to disregard the others. I will vote up for the rest and I am really sorry for the confusion. I am writing from my phone sometimes I messed up something cause of scroll and mixing replies. Not sure what I did. – Germán Diago Aug 29 '16 at 14:33