Reputation: 665
First, is it even possible to return different types depending on some condition? Second, is there a more concise way to handle this kind of mapping, without enumerating all possible if's and else's?
here are lots and lots (and lots) of if's, and's and but's
Basically, I want to define an incomplete tuple, which I want to be automatically extended to complete with some default types. The completeness is related to some context, of course. Here, B0 and B1 will be defaults. And the complete tuple must have the form <B0 or its derived, B1 or its derived>
. A tuple in my work has more than two elements, actually.
struct B0 { };
struct B1 { };
// here, a complete tuple is a tuple that has two elements:
// of B0 type or its derived as a 1st element and
// of B1 type or its derived as a 2nd element
// templ argumets can be in the order B0, B1 or its derived or absent
template<class Tuple>
CompleteTuple make_complete_tuple()
{
if (std::tuple_size<Tuple>::value == 0)
return std::tuple<B0,B1>{};
else if (std::tuple_size<Tuple>::value == 1)
{
using ElemT = std::tuple_element<0, Tuple>::type;
if (std::is_base_of<B0, ElemT>::value)
return std::tuple<ElemT, B1>{};
if (std::is_base_of<B1, ElemT>::value)
return std::tuple<B0, ElemT>{};
}
else if (std::tuple_size<Tuple>::value == 2)
{
using Elem0T = std::tuple_element<0, Tuple>::type;
using Elem1T = std::tuple_element<1, Tuple>::type;
if (std::is_base_of<B0, Elem0T>::value)
if(std::is_base_of<B1, Elem1T>::value)
return std::tuple<Elem0T, Elem1T>{};
// not handling another conditions, the example is only for conveying idea
}
}
struct A : public B0 { };
struct C : public B1 { };
int main()
{
auto complete_tuple0 = make_complete_tuple<std::tuple<A>>();
// complete_tuple0 shoud be std::tuple<A, B1>;
auto complete_tuple1 = make_complete_tuple<std::tuple<C>>();
// complete_tuple1 shoud be std::tuple<B0, C>;
// etc
std::cin.get();
return 0;
}
Upvotes: 1
Views: 370
Reputation: 1941
In addition to @Quimby's brilliant solution I would like to show mine. Just used auto return type and added constexpr and if constexpr everywhere. Also used _v and _t to make the code shorter. So, it will compile since C++17.
#include <tuple>
#include <iostream>
struct B0 { };
struct B1 { };
template<class Tuple>
constexpr auto make_complete_tuple()
{
if constexpr (std::tuple_size_v<Tuple> == 0)
return std::tuple<B0,B1>{};
else if constexpr (std::tuple_size_v<Tuple> == 1)
{
using ElemT = std::tuple_element_t<0, Tuple>;
if constexpr (std::is_base_of_v<B0, ElemT>)
return std::tuple<ElemT, B1>{};
if constexpr (std::is_base_of_v<B1, ElemT>)
return std::tuple<B0, ElemT>{};
}
else if constexpr (std::tuple_size<Tuple>::value == 2)
{
using Elem0T = std::tuple_element_t<0, Tuple>;
using Elem1T = std::tuple_element_t<1, Tuple>;
if constexpr(std::is_base_of_v<B0, Elem0T>)
if constexpr(std::is_base_of_v<B1, Elem1T>)
return std::tuple<Elem0T, Elem1T>{};
// not handling another conditions, the example is only for conveying idea
}
}
struct A : public B0 { };
struct C : public B1 { };
int main()
{
auto complete_tuple0 = make_complete_tuple<std::tuple<A>>();
// complete_tuple0 shoud be std::tuple<A, B1>;
static_assert(std::is_same_v<decltype(complete_tuple0),std::tuple<A, B1>>);
auto complete_tuple1 = make_complete_tuple<std::tuple<C>>();
// complete_tuple1 shoud be std::tuple<B0, C>;
static_assert(std::is_same_v<decltype(complete_tuple1),std::tuple<B0, C>>);
return 0;
}
Upvotes: 3
Reputation: 19113
C++17 solution, C++11 if you replace _v
usings
with ::value
.
Does not require distinct bases, but it will always replace each default base in the default tuple by the left-most matching type in the passed tuple argument.
#include <tuple>
#include <type_traits>
// Returns the first type from Ts that is derived from Base. Returns Base if there is not one.
template<typename Base,typename...Ts>
struct pick_derived;
// No Ts were derived from Base.
template<typename Base>
struct pick_derived<Base>{
using type=Base;
};
template<typename Base,typename Derived, typename...Tail>
struct pick_derived<Base,Derived,Tail...>{
using type = typename std::conditional<std::is_base_of_v<Base,Derived>,
Derived,// Return it. Otherwise continue searching.
typename pick_derived<Base,Tail...>::type>::type;
};
template<typename SourceTuple, typename DefaultTuple>
struct tup_transformer_impl;
template<typename...Ts, typename...Ds>
struct tup_transformer_impl<std::tuple<Ts...>,std::tuple<Ds...>>{
// Fancy double pack expansion
// For each default Ds type, try to replace it with a derived type from Ts.
using type = std::tuple<typename pick_derived<Ds,Ts...>::type...>;
};
#include <iostream>
struct B0 { };
struct B1 { };
// Tweak this.
using default_tuple = std::tuple<B0,B1>;
template<typename Tuple>
using tup_transform = typename tup_transformer_impl<Tuple,default_tuple>::type;
template<class Tuple>
tup_transform<Tuple> make_complete_tuple()
{
return {};
}
struct A : public B0 { };
struct C : public B1 { };
int main()
{
auto complete_tuple0 = make_complete_tuple<std::tuple<A>>();
// complete_tuple0 shoud be std::tuple<A, B1>;
static_assert(std::is_same_v<decltype(complete_tuple0),std::tuple<A, B1>>);
auto complete_tuple1 = make_complete_tuple<std::tuple<C>>();
// complete_tuple1 shoud be std::tuple<B0, C>;
static_assert(std::is_same_v<decltype(complete_tuple1),std::tuple<B0, C>>);
std::cin.get();
return 0;
}
I guess you can just call it like:
tup_transform<std::tuple<A>> complete_tuple0;
( Changing it to tup_transform<A> complete_tuple0;
is quite easy. Just change tup_transformer_impl to use Ds...
directly instead of unpacking them.)
Of course, all this requires that each type is default constructible. If that is not the case, I think the solution can be adapted to work if the derived types are at least movable. In that case each derived value would have to be passed to make_complete_tuple
function.
Upvotes: 3