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:
main
function), how can I fix them?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 constexpr
s because of the static_assert
s, 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 decltype
s, 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) constexpr
s.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With