Related
I am attempting to convert between two types of std::tuples but I'm having trouble getting the implementation right. I want it to map if the type is the same, but it needs to allow duplicate types to get mapped in the original order.
The basic logic is:
while has pair<input, output>
if type(input) == type(output)
then do map, next input, next output
else
next input
assert(all outputs mapped)
With an example mapping being:
My "best" attempt at a solution looks like this:
template <auto> struct value {};
template <auto... Vals> struct value_sequence {};
template <class... Vals> struct placeholder {};
template <auto... As, auto... Bs>
constexpr value_sequence<As..., Bs...> operator+(value_sequence<As...>, value_sequence<Bs...>)
{
return {};
}
template <size_t Idx, size_t... Idxs, size_t OtherIdx, size_t... OtherIdxs, class T, class... Ts, class OtherT, class... OtherTs>
constexpr auto mapper(const std::index_sequence<Idx, Idxs...>&, const std::index_sequence<OtherIdx, OtherIdxs...>&, const placeholder<T, Ts...>&,
const placeholder<OtherT, OtherTs...>&)
{
if constexpr (sizeof...(OtherIdxs) == 0)
{
static_assert(std::is_same_v<T, OtherT>);
return value_sequence<Idx>{};
}
else if constexpr (std::is_same_v<T, OtherT>)
{
return value_sequence<Idx>{} +
mapper(std::index_sequence<Idxs...>{}, std::index_sequence<OtherIdxs...>{}, placeholder<Ts...>{}, placeholder<OtherTs...>{});
}
else
{
return mapper(std::index_sequence<Idx, Idxs...>{}, std::index_sequence<OtherIdxs...>{}, placeholder<T, Ts...>{}, placeholder<OtherTs...>{});
}
}
Called with:
mapper(std::make_index_sequence<sizeof...(Ts)>{}, std::make_index_sequence<sizeof...(OtherTs)>{},
placeholder<Ts...>{}, placeholder<OtherTs...>{})
Which gives the compiler error error C2672: 'mapper': no matching overloaded function found pointing to the else if case of the function mapper
I'm working in c++17, and it looks like all the bits I need are there, I just can't assemble them the right way.
Any help would be really appreciated!
Here's a fairly simple recursive implementation:
// pop_front implementation taken from https://stackoverflow.com/a/39101723/4151599
template <typename Tuple, std::size_t... Is>
auto pop_front_impl(const Tuple& tuple, std::index_sequence<Is...>)
{
return std::make_tuple(std::get<1 + Is>(tuple)...);
}
template <typename Tuple>
auto pop_front(const Tuple& tuple)
{
return pop_front_impl(tuple,
std::make_index_sequence<std::tuple_size<Tuple>::value - 1>());
}
template <typename...>
std::tuple<> map_tuple(std::tuple<>)
{
return {};
}
template <typename FirstOutput, typename... Outputs,
typename FirstInput, typename... Inputs>
std::tuple<FirstOutput, Outputs...>
map_tuple(const std::tuple<FirstInput, Inputs...>& input)
{
if constexpr (std::is_same_v<FirstInput, FirstOutput>) {
return std::tuple_cat(
std::tuple<FirstOutput>(std::get<0>(input)),
map_tuple<Outputs...>(pop_front(input))
);
} else {
return map_tuple<FirstOutput, Outputs...>(pop_front(input));
}
}
Simply call it like
std::tuple<int, double, char, int, int, float> tup = {1, 2.0, '3', 4, 5, 6.0};
auto mapped = map_tuple<int, double, char, float>(tup);
Demo
To begin, need a helper template to peel off the first type from a tuple.
#include <utility>
#include <type_traits>
#include <tuple>
#include <cstdlib>
#include <iostream>
template<typename T>
struct peel_tuple_t;
template<typename T, typename ...Args>
struct peel_tuple_t< std::tuple<T, Args...>> {
typedef std::tuple<Args...> type_t;
};
template<typename T>
using peel_tuple=typename peel_tuple_t<T>::type_t;
static_assert(std::is_same_v< peel_tuple<std::tuple<int, float>>,
std::tuple<float>>);
That's simple enough: std::tuple<int, float> -> std::tuple<float>.
Next, we need an equivalent of tuple_cat, but for index_sequences:
template<typename T, typename T2> struct index_sequence_cat_t;
template<size_t ...A, size_t ...B>
struct index_sequence_cat_t<std::index_sequence<A...>,
std::index_sequence<B...>> {
typedef std::index_sequence<A..., B...> type_t;
};
template<typename T, typename T2>
using index_sequence_cat=typename index_sequence_cat_t<T, T2>::type_t;
static_assert(std::is_same_v<index_sequence_cat<
std::index_sequence<1, 2>,
std::index_sequence<3, 4>
>, std::index_sequence<1, 2, 3, 4>>);
We can now use this to take a std::tuple<float, int, double, double>>, which provides the values, then a std::tuple<float, double, double>, where the values go, and produce a std::index_sequence<0, 2, 3> that will tell us to std::get index 0, 2, and 3 from the source tuple into the destination tuple:
template<typename output_tuple, typename input_tuple, size_t index>
struct map_input_tuple_t : map_input_tuple_t<output_tuple,
peel_tuple<input_tuple>,
index+1> {};
template<typename T, typename ...Args2, size_t index>
struct map_input_tuple_t<std::tuple<T>,
std::tuple<T, Args2...>, index> {
typedef std::index_sequence<index> type_t;
};
template<typename T, typename ...Args, typename ...Args2, size_t index>
struct map_input_tuple_t<std::tuple<T, Args...>,
std::tuple<T, Args2...>, index> {
typedef index_sequence_cat<
std::index_sequence<index>,
typename map_input_tuple_t<std::tuple<Args...>,
std::tuple<Args2...>,
index+1>::type_t> type_t;
};
template<typename output_tuple, typename input_tuple>
using map_input_tuple=
typename map_input_tuple_t<output_tuple, input_tuple, 0>::type_t;
static_assert(std::is_same_v< map_input_tuple<
std::tuple<float, double, double>,
std::tuple<float, int, double, double>>,
std::index_sequence<0, 2, 3>>);
And now, this is a solved problem:
template<typename mapping> struct map_tuple_impl;
template<size_t ...n>
struct map_tuple_impl<std::index_sequence<n...>> {
template<typename T>
static auto doit(const T &t)
{
return std::tuple{ std::get<n>(t)... };
}
};
template<typename output_tuple, typename ...input_types>
auto map_tuple(const std::tuple<input_types...> &input)
{
return map_tuple_impl<map_input_tuple<output_tuple,
std::tuple<input_types...>>>
::doit(input);
}
int main()
{
auto t=map_tuple<std::tuple<float, double, double>
>(std::tuple{1.0f, 2,
2.0,
3.0});
static_assert(std::is_same_v<decltype(t),
std::tuple<float, double, double>>);
if (t == std::tuple{1.0F, 2.0, 3.0})
std::cout << "yes\n";
return 0;
}
Is too late to play?
I propose the following struct to select the matching indexes
template <typename, typename, std::size_t...>
struct get_match_ind;
template <typename A0, typename ... As, typename ... Bs,
std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<A0, As...>, std::tuple<A0, Bs...>, I0, Is...>
: public get_match_ind<std::tuple<As...>, std::tuple<Bs...>, I0+1u, Is..., I0>
{ };
template <typename A0, typename ... As, typename ... Bs,
std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<A0, As...>, std::tuple<Bs...>, I0, Is...>
: public get_match_ind<std::tuple<As...>, std::tuple<Bs...>, I0+1u, Is...>
{ };
template <typename ... As, std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<As...>, std::tuple<>, I0, Is...>
{ using type = std::index_sequence<Is...>; };
template <typename T1, typename T2>
using match_indexes = typename get_match_ind<T1, T2, 0u>::type;
Now a map_tuple() function with a simple helper
template <typename Tb, typename Ta, std::size_t ... Is>
auto map_tuple_helper (Ta const & t0, std::index_sequence<Is...> const &)
{ return Tb{ std::get<Is>(t0)... }; }
template <typename Tb, typename Ta>
auto map_tuple (Ta const & t0)
{ return map_tuple_helper<Tb>(t0, match_indexes<Ta, Tb>{}); }
The following is a full compiling C++17 example with a static_assert() to verify the mapping
#include <tuple>
template <typename, typename, std::size_t...>
struct get_match_ind;
template <typename A0, typename ... As, typename ... Bs,
std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<A0, As...>, std::tuple<A0, Bs...>, I0, Is...>
: public get_match_ind<std::tuple<As...>, std::tuple<Bs...>, I0+1u, Is..., I0>
{ };
template <typename A0, typename ... As, typename ... Bs,
std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<A0, As...>, std::tuple<Bs...>, I0, Is...>
: public get_match_ind<std::tuple<As...>, std::tuple<Bs...>, I0+1u, Is...>
{ };
template <typename ... As, std::size_t I0, std::size_t... Is>
struct get_match_ind<std::tuple<As...>, std::tuple<>, I0, Is...>
{ using type = std::index_sequence<Is...>; };
template <typename T1, typename T2>
using match_indexes = typename get_match_ind<T1, T2, 0u>::type;
template <typename Tb, typename Ta, std::size_t ... Is>
auto map_tuple_helper (Ta const & t0, std::index_sequence<Is...> const &)
{ return Tb{ std::get<Is>(t0)... }; }
template <typename Tb, typename Ta>
auto map_tuple (Ta const & t0)
{ return map_tuple_helper<Tb>(t0, match_indexes<Ta, Tb>{}); }
int main()
{
// target type
using TT = std::tuple<int, double, char, float>;
std::tuple<int, double, char, int, int, float> t0 {1, 2.0, '3', 4, 5, 6.0};
auto t1 { map_tuple<TT>(t0) };
using T1 = decltype(t1);
static_assert( std::is_same_v<TT, T1> );
}
I would like my class
template <class T, unsigned int n>
class X;
to create a std::tuple that contains n times the type T. Is there a particularly neat way for this? Is there also a nice way to do this for arbitrary variadic template classes?
This is what I did first:
#include <tuple>
template <class, unsigned int, class>
struct simple_repeat_helper;
template <class T, unsigned int n, class... Args>
struct simple_repeat_helper<T, n, std::tuple<Args...>>
{
typedef typename simple_repeat_helper<T, n-1, std::tuple<Args..., T>>::type type;
};
template <class T, class... Args>
struct simple_repeat_helper<T, 0, std::tuple<Args...>>
{
typedef std::tuple<Args...> type;
};
template <class T, unsigned int n>
struct simple_repeat
{
using type = typename simple_repeat_helper<T, n, std::tuple<>>::type;
};
But actually, I do not need this for std::tuple, but for another class that acts similarly. So I thought that I would create a version that is a little bit more generic:
template <class, unsigned int, template <class...> class, class>
struct repeat_helper;
template <class T, template <class...> class M, class... Args>
struct repeat_helper<T, 0, M, M<Args...>>
{
typedef M<Args...> type;
};
template <class T, unsigned int n, template <class...> class M, class... Args>
struct repeat_helper<T, n, M, M<Args...>>
{
typedef typename repeat_helper<T, n-1, M, M<Args..., T>>::type type;
};
template <class T, unsigned int n, template <class...> class M = std::tuple>
struct repeat
{
using type = typename repeat_helper<T, n, M, M<>>::type;
};
I thought that I could use it like this:
repeat<double, 5, std::tuple>::type x = std::make_tuple( 1., 2., 3., 4., 5. );
But unfortunately it fails to compile due to:
ambiguous class template instantiation for ‘struct repeat_helper<double, 0u, std::tuple, std::tuple<double, double, double, double, double> >’
Any help on this error would be appreciated!
I would do it this way:
template<typename, typename>
struct append_to_type_seq { };
template<typename T, typename... Ts, template<typename...> class TT>
struct append_to_type_seq<T, TT<Ts...>>
{
using type = TT<Ts..., T>;
};
template<typename T, unsigned int N, template<typename...> class TT>
struct repeat
{
using type = typename
append_to_type_seq<
T,
typename repeat<T, N-1, TT>::type
>::type;
};
template<typename T, template<typename...> class TT>
struct repeat<T, 0, TT>
{
using type = TT<>;
};
As a small test:
#include <type_traits>
#include <tuple>
template<typename... Ts>
struct X { };
int main()
{
repeat<double, 5, std::tuple>::type t = std::make_tuple(1., 2., 3., 4., 5.);
static_assert(
std::is_same<
decltype(t),
std::tuple<double, double, double, double, double>
>::value, "!");
repeat<double, 3, X>::type y;
static_assert(
std::is_same<decltype(y), X<double, double, double>>::value, "!");
}
Finally, a live example.
An indices-based solution:
template<typename Dependent, int Index>
using DependOn = Dependent;
// Assuming e.g. Indices<3> is indices<0, 1, 2>
template<typename T, int N, typename I = Indices<N>>
struct repeat;
template<typename T, int N, int... Indices>
struct repeat<T, N, indices<Indices...>> {
// Can be an actual type-list instead of (ab)using std::tuple
using type = std::tuple<DependOn<T, Indices>...>;
};
C++14 flavour:
template<typename Dependent, std::size_t Index>
using DependOn = Dependent;
template<typename T, std::size_t N, typename Indices = std::make_index_sequence<N>>
struct repeat;
template<typename T, std::size_t N, std::size_t... Indices>
struct repeat<T, N, std::index_sequence<Indices...>> {
using type = std::tuple<DependOn<T, Indices>...>;
};
If you look at the member type of
template <typename Pack> struct flatten;
template <typename T, typename U, std::size_t A, std::size_t B, std::size_t C, typename V, std::size_t D, std::size_t E, typename W>
struct flatten<std::tuple<T,U, std::index_sequence<A,B,C>, V, std::index_sequence<D,E>, W>> {
template <typename, typename, std::size_t, std::size_t, std::size_t, typename, std::size_t, std::size_t, typename> struct S;
using type = S<T,U,A,B,C,V,D,E,W>;
};
is it possible to do this generically, thus flattening all types and non-types into a single flattened pack, as long as the non-types are wrapped within a type?
Here is my simple solution to the normal type of flattening, and I'm just wondering how to use this method to work the above generically.
template <typename T> struct identity { using type = T; };
template <typename...> struct merge;
template <typename Pack>
struct merge<Pack> : identity<Pack> {};
template <template <typename...> class P, typename... Ts, typename... Us>
struct merge<P<Ts...>, P<Us...>> : identity<P<Ts..., Us...>> {};
template <typename First, typename... Rest>
struct merge<First, Rest...> : merge<First, typename merge<Rest...>::type> {};
template <typename T, template <typename...> class P> struct flatten_h : identity<P<T>> {};
template <template <typename...> class P, typename... Ts>
struct flatten_h<P<Ts...>, P> : merge<typename flatten_h<Ts, P>::type...> {};
template <typename Pack> struct flatten;
template <template <typename...> class P, typename... Ts>
struct flatten<P<Ts...>> : flatten_h<P<Ts...>, P> {};
// Testing
#include <type_traits>
template <typename...> struct P;
int main() {
static_assert(std::is_same<
flatten<P<int, char, long, P<double, bool>, int>>::type,
P<int, char, long, double, bool, int>
>::value);
static_assert(std::is_same<
flatten<P<int, P<bool, bool>, long, P<double, P<char, P<long>>>, int>>::type,
P<int, bool, bool, long, double, char, long, int>
>::value);
}
I think C++20 should allow auto... (or some other keyword) to indicate both types and non-type values for template arguments.
The task becomes feasible if each non-type parameter (unpacked from, say, an std::index_sequence) can be wrapped in its own std::integral_constant. Then there are only type template parameters at the level where the flattening occurs and one may use a simple type container like template<class...> struct Types {};.
// upper version for shorter type names; lower version for showing types
template<auto v> struct Val : std::integral_constant<decltype(v), v> {};
//template<auto v> using Val = std::integral_constant<decltype(v), v>;
// NOTE: bug(?) in GCC 7.2 which maps size_t(0) to false and size_t(1) to true
template<class I, I... is>
auto flatten(Type< std::integer_sequence<I, is...> >) {
return Flattened<Val<is>...>{};
}
Below and in a live demo you find a full working example with that restriction (and the bug(?) mentioned in the comment).
I chose to specify all types which should be flattened. Alternatively, one may also blindly unpack "everything reasonable" using various template template arguments of the form template<template<auto, class...> class ToFlatten> etc.
#include <iostream>
#include <tuple>
#include <utility>
// upper version for shorter type names; lower version for showing types
template<auto v> struct Val : std::integral_constant<decltype(v), v> {};
//template<auto v> using Val = std::integral_constant<decltype(v), v>;
// NOTE: bug(?) in GCC 7.2 which maps size_t(0) to false and size_t(1) to true
template<size_t... is, class F>
constexpr decltype(auto) indexer_impl(std::index_sequence<is...>, F f) {
return f(Val<is>{}...);
}
template<size_t size, class F>
constexpr decltype(auto) indexer(F f) {
return indexer_impl(std::make_index_sequence<size>{}, f);
}
////////////////////////////////////////////////////////////////////////////////
template<class T_>
struct Type {};
template<class... Ts>
struct Types {
static constexpr auto size = Val<sizeof...(Ts)>{};
using Tuple = std::tuple<Ts...>;
};
template<size_t i, class T>
using at_t = std::tuple_element_t<i, typename T::Tuple>;
////////////////////////////////////////////////////////////////////////////////
template<class...> struct Flattened;
template<class I, I... is> using int_seq = std::integer_sequence<I, is...>;
// specify which types are allowed in a flat type container
template<class T> struct is_flat : Val<true> {};
template<class I, I... is> struct is_flat< int_seq<I, is...> > : Val<false> {};
template<class... Ts> struct is_flat< Types<Ts...> > : Val<false> {};
template<class... Ts> struct is_flat< Flattened<Ts...> > : Val<false> {};
// check if a type is an instantiation of `template<class...> struct Flattened`
template<class T> struct is_flattened : Val<false> {};
template<class... Ts> struct is_flattened<Flattened<Ts...>> : Val<true> {};
// specific type container which guarantees to contain `is_flat` types only
template<class... Ts> struct Flattened : Types<Ts...> {
static_assert((... && is_flat<Ts>{}));
};
////////////////////////////////////////////////////////////////////////////////
namespace internal {
auto merge() {
return Flattened<>{};
}
template<class... Ts>
auto merge(Flattened<Ts...> done) {
return done;
}
template<class... Ts, class... Us>
auto merge(Flattened<Ts...>, Flattened<Us...>) {
return Flattened<Ts..., Us...>{};
}
// merge more than two args: attempt to avoid linear recursion: is it better?
template<class... Ts, class... Fs>
auto merge(Flattened<Ts...>, Fs...) {
static_assert((... && is_flattened<Fs>{}));
using T = Types<Flattened<Ts...>, Fs...>;
// group the Flattened args into two halves
constexpr size_t N = T::size;
constexpr size_t N0 = N/2u;
constexpr size_t N1 = N-N0;
auto h0 = indexer<N0>([] (auto... is) { return merge(at_t<is, T>{}...); });
auto h1 = indexer<N1>([] (auto... is) { return merge(at_t<N0+is, T>{}...); });
return merge(h0, h1);
}
template<class T>
auto flatten(Type<T>) {
static_assert(is_flat<T>{});
return Flattened<T>{};
}
template<class I, I... is>
auto flatten(Type< std::integer_sequence<I, is...> >) {
return Flattened<Val<is>...>{};
}
template<class... Ts>
auto flatten(Type< Types<Ts...> >) {
return merge(internal::flatten(Type<Ts>{})...);
}
}// internal
template<class... Ts>
auto flatten(Types<Ts...>) {
return internal::merge(internal::flatten(Type<Ts>{})...);
}
////////////////////////////////////////////////////////////////////////////////
template<class T>
void inspect() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
struct Custom {};
int main() {
auto foo = Types<
Types<int, char, long>,
Val<7>,
Types<double, Val<5>, float, Types<unsigned, Types<Custom, Types<char>, int>>, std::make_index_sequence<4u>>,
std::index_sequence<5u,19u,4u>,
Types<>,
Val<8>
>{};
auto bar = flatten(foo);
inspect<decltype(bar)>();
return 0;
}
Output:
void inspect() [with T = Flattened<int, char, long int, Val<7>, double, Val<5>, float, unsigned int, Custom, char, int, Val<false>, Val<true>, Val<2>, Val<3>, Val<5>, Val<19>, Val<4>, Val<8> >]
Output with longer type names:
void inspect() [with T = Flattened<int, char, long int, std::integral_constant<int, 7>, double, std::integral_constant<int, 5>, float, unsigned int, Custom, char, int, std::integral_constant<bool, false>, std::integral_constant<bool, true>, std::integral_constant<long unsigned int, 2>, std::integral_constant<long unsigned int, 3>, std::integral_constant<int, 5>, std::integral_constant<long unsigned int, 19>, std::integral_constant<long unsigned int, 4>, std::integral_constant<int, 8> >]
take_from_args<foo<int, bool, char, float>, 0,2>::type is to be
foo<int, char> based on the positions 0 and 2. The implementation is easy:
template <typename Class, std::size_t... Positions>
struct take_from_args;
template <template <typename...> class P, typename... Ts, std::size_t... Is>
struct take_from_args<P<Ts...>, Is...> {
using type = P<std::tuple_element_t<Is, std::tuple<Ts...>>...>;
};
Now, let's try to apply this to this class:
template <int V, bool B, typename... Args>
struct bar {};
The problem is the int and bool parameters of bar, so it cannot be passed into take_from_args. So let's define:
template <int V, bool B>
struct bar_h {
template <typename... Args>
using templ = bar<V, B, Args...>;
};
Unfortunately, take_from_args<bar_h<5, true>::templ<int, bool, char, float>, 0,2>::type won't compile. How do I redefine take_from_args so that it can template classes like bar?
My entire code:
#include <tuple>
template <typename Class, std::size_t... Positions> struct take_from_args;
template <template <typename...> class P, typename... Ts, std::size_t... Is>
struct take_from_args<P<Ts...>, Is...> {
using type = P<std::tuple_element_t<Is, std::tuple<Ts...>>...>;
};
// Testing
template <typename... Args>
struct foo {};
template <int V, bool B, typename... Args>
struct bar {};
template <int V, bool B>
struct bar_h {
template <typename... Args>
using templ = bar<V, B, Args...>;
};
int main() {
static_assert(std::is_same<
take_from_args<foo<int, bool, char, float>, 0,2>::type,
foo<int, char>>::value);
// static_assert(std::is_same<
// take_from_args<bar_h<5, true>::templ<int, bool, char, float>, 0,2>::type,
// bar<5, true, int, char>>::value);
}
Your workaround with bar_h does not work because bar_h<5, true>::templ<int, bool, char, float> is just an alias for bar<5, true, int, bool, char, float>:
static_assert(// compiles without error
std::is_same_v<
bar_h<5, true>::templ<int, bool, char, float>,
bar<5, true, int, bool, char, float>
>
);
I see two options:
1. avoid non-type template arguments in `bar`
see std::integral_constant
Edit: As you followed this approach in your answer, but I experience issues with clang: Here is a modified version which works for me with GCC 7.2 and Clang 5.0.
template<auto...> struct Vals {};
template<class T>
struct HasVals : std::false_type {};
template<auto... Vs>
struct HasVals<Vals<Vs...>> : std::true_type {};
template<class T, size_t... is>
struct take_from_args;
template<template<class...> class P, class... Ts, size_t... is>
struct take_from_args<P<Ts...>, is...> {
// convention: pass-through first argument if it `HasVals`
using Vs = std::tuple_element_t<0, std::tuple<Ts...>>;
using type = std::conditional_t<
HasVals<Vs>::value,
P<Vs, std::tuple_element_t<1u+is, std::tuple<Ts...>>...>,
P<std::tuple_element_t<is, std::tuple<Ts...>>...>
>;
};
// Testing
template<class Vs, class... Args>
struct bar;
template<int v, bool b, class... Args>
struct bar<Vals<v, b>, Args...> {
static constexpr int value = v;
static constexpr bool truth = b;
};
2. provide more specialization for `take_from_args`
Since you specifically ask for the latter, here is an example:
// there could be 1 value(s) at the beginning...
template<
template<auto, auto, typename...> class P,
auto v0, class... Ts, std::size_t... is
> struct take_from_args<P<v0, Ts...>, is...> {
using type = P<v0, std::tuple_element_t<is, std::tuple<Ts...>>...>;
};
// ... 2 ...
template<
template<auto, auto, typename...> class P,
auto v0, auto v1, class... Ts, std::size_t... is
> struct take_from_args<P<v0, v1, Ts...>, is...> {
using type = P<v0, v1, std::tuple_element_t<is, std::tuple<Ts...>>...>;
};
// ... 3 ... and more?
template<
template<auto, auto, typename...> class P,
auto v0, auto v1, auto v2, class... Ts, std::size_t... is
> struct take_from_args<P<v0, v1, v2, Ts...>, is...> {
using type = P<v0, v1, v2, std::tuple_element_t<is, std::tuple<Ts...>>...>;
};
Unfortunately, I had no success using auto... for the deduction of leading template arguments.
Following Julius' last suggestion for redesigning the class bar:
#include <tuple>
#include <type_traits>
template <auto...> struct Vals {};
template <typename Class> struct HasVals : std::false_type {};
template <auto... Vs>
struct HasVals<Vals<Vs...>> : std::true_type {};
template <typename Class, std::size_t... Positions> struct take_from_args;
template <template <typename, typename...> class P, typename VALS, typename... Ts, std::size_t... Is>
struct take_from_args<P<VALS, Ts...>, Is...> {
using type = std::conditional_t<HasVals<VALS>::value,
P<VALS, std::tuple_element_t<Is, std::tuple<Ts...>>...>,
P<std::tuple_element_t<Is, std::tuple<VALS, Ts...>>...> // VALS is part of the tuple in this case
>;
};
// Testing
template <typename... Args>
struct foo {};
template <typename VALS, typename... Args>
struct bar;
template <int v, bool b, typename... Args>
struct bar<Vals<v, b>, Args...> {
static constexpr int value = v;
static constexpr bool truth = b;
};
int main() {
static_assert(std::is_same<
take_from_args<foo<int, bool, char, float>, 0,2>::type,
foo<int, char>>::value);
using new_bar = take_from_args<bar<Vals<5, true>, int, bool, char, float>, 0,2>::type;
static_assert(std::is_same<
new_bar,
bar<Vals<5, true>, int, char>>::value);
static_assert(new_bar::value == 5);
static_assert(new_bar::truth == true);
//static_assert(std::is_same<decltype(new_bar::value), int>::value); // why fails?
//static_assert(std::is_same<decltype(new_bar::truth), bool>::value);
}
As this question doesn't seem to cover all useful cases I decided to fill the gap with this little question of mine. Is there a way to answer if two multisets of types are equal?
#include <tuple>
#include <type_traits>
template <typename, typename>
struct type_multiset_eq : std::false_type
{
};
template <typename ... Types1, typename ... Types2>
struct type_multiset_eq<std::tuple<Types1...>, std::tuple<Types2...>>
: std::true_type
{
// Should only be true_type if the multisets of types are equal
};
int main() {
static_assert(type_multiset_eq<std::tuple<char, int, double, float, int, float>, std::tuple<float, char, int, double, int, float>>::value, "err");
static_assert(!type_multiset_eq<std::tuple<char, int, double, float, int, float>, std::tuple<char, int, double, int, float>>::value, "err");
static_assert(type_multiset_eq<std::tuple<char, char, char, float, float, float>, std::tuple<char, float, char, float, char, float>>::value, "err");
static_assert(!type_multiset_eq<std::tuple<int, int>, std::tuple<int, int, int>>::value, "err");
}
In the answer I focused a bit on efficiency. The method can be divided on four basic steps:
Rank types in each pack
Sort types in packs in base of given rank
Create two sets of unique elements (also types) containing information about the types and its frequencies from the previous version of packs (inheriting from these types)
Investigate if the other multiset of types is deriving from the same types (with frequencies)
The approach should be O(N log N) depending on the number of type
C++14 approach:
#include <utility>
#include <array>
template <class T>
constexpr T ilog2(T n) {
return n == static_cast<T>(1) ? static_cast<T>(0) : ilog2(n >> static_cast<T>(1)) + 1;
}
template <class T>
constexpr T ipow2(T n) {
return static_cast<T>(1) << n;
}
template <std::size_t N>
struct s_exp {
static constexpr std::size_t exp = ipow2(ilog2(N-1)+1);
};
template <std::size_t I, class T>
struct itag { };
template <std::size_t D, std::size_t I, class T>
struct vvtag { };
template <std::size_t S, std::size_t I, class T>
struct vtag: virtual vvtag<I/S, (I%S) / ((s_exp<S>::exp + (2 << (I/S)) - 1)/(2 << (I/S))), T> { };
template <class... Ts>
struct pack {
static constexpr std::size_t size = sizeof...(Ts);
};
template <class P, class = std::make_index_sequence<P::size>>
struct ipack;
template <class... Ts, std::size_t... Is>
struct ipack<pack<Ts...>, std::index_sequence<Is...>>: itag<Is, Ts>... {
static constexpr std::size_t size = sizeof...(Ts);
};
template <std::size_t I, class T>
T ipack_element(itag<I, T>);
template <class IP, class = std::make_index_sequence<IP::size * (ilog2(IP::size - 1) + 1) >>
struct vpack;
template <class IP, std::size_t... Is>
struct vpack<IP, std::index_sequence<Is...>>: vtag<IP::size, Is, decltype(ipack_element<Is % IP::size>(IP{}))>... {
static constexpr std::size_t size = IP::size;
};
template <class A, class CompArr>
constexpr int partition(A &a, int lo, int hi, const CompArr &ca) {
int x = a[lo];
int i = lo, j = hi;
while (true) {
while (ca[a[j]] > ca[x])
j--;
while (ca[a[i]] < ca[x])
i++;
if (i < j) {
auto w = a[i];
a[i] = a[j];
a[j] = w;
i++;
j--;
} else
return j;
}
}
template <class A, class CompArr>
constexpr void quicksort(A &a, int lo, int hi, const CompArr &ca) {
if (lo < hi) {
auto q = partition(a, lo, hi, ca);
quicksort(a, lo, q, ca);
quicksort(a, q+1, hi, ca);
}
}
template <class... Ts, std::size_t... Is>
constexpr std::array<std::size_t, sizeof...(Ts)> rank(itag<0, ipack<pack<Ts...>, std::index_sequence<Is...>>>) {
return {{!std::is_base_of<vvtag<0, 0, decltype(ipack_element<Is>(ipack<pack<Ts...>>{}))>, vpack<ipack<pack<Ts...>>>>::value...}};
}
template <std::size_t N, class... Ts, std::size_t... Is>
constexpr std::array<std::size_t, sizeof...(Ts)> rank(itag<N, ipack<pack<Ts...>, std::index_sequence<Is...>>>) {
constexpr auto prev = rank(itag<N - 1, ipack<pack<Ts...>>>{});
return {{prev[Is]*2 + !std::is_base_of<vvtag<N, prev[Is]*2, decltype(ipack_element<Is>(ipack<pack<Ts...>>{}))>, vpack<ipack<pack<Ts...>>>>::value...}};
}
template <class... Ts, std::size_t... Is>
constexpr std::array<std::size_t, sizeof...(Ts)> sort_types_impl(ipack<pack<Ts...>, std::index_sequence<Is...>>) {
constexpr std::size_t TS = sizeof...(Ts);
auto compare_enabler = rank(itag<ilog2(TS - 1), ipack<pack<Ts...>, std::index_sequence<Is...>>>{});
std::size_t result[TS] { Is... };
quicksort(result, 0, sizeof...(Is) - 1, compare_enabler);
return {{ result[Is]... }};
}
template <class>
struct sort_types;
template <class... Ts>
struct sort_types<pack<Ts...>>: sort_types<ipack<pack<Ts...>>> { };
template <class... Ts, std::size_t... Is>
struct sort_types<ipack<pack<Ts...>, std::index_sequence<Is...>>> {
static constexpr auto idxs = sort_types_impl(ipack<pack<Ts...>>{});
using type = pack<decltype(ipack_element<idxs[Is]>(ipack<pack<Ts...>>{}))...>;
};
struct dummy { };
template <class... Ts>
struct unique_pack: Ts... {
static constexpr std::size_t size = sizeof...(Ts);
template <class Up>
constexpr bool operator==(Up) {
bool result = size == Up::size;
bool ibo[sizeof...(Ts)] = { std::is_base_of<Ts, Up>::value... };
for (std::size_t i = 0; i < sizeof...(Ts); i++)
result &= ibo[i];
return result;
}
};
template <class>
struct multiset;
template <class... Ts>
struct multiset<pack<Ts...>>: multiset<ipack<pack<Ts...>>> {};
template <class... Ts, std::size_t... Is>
struct multiset<ipack<pack<Ts...>, std::index_sequence<Is...>>> {
using sorted_pack = typename sort_types<pack<Ts..., dummy>>::type;
static constexpr std::array<bool, sizeof...(Ts)> const unique_types() {
return {{ !std::is_same< decltype(ipack_element<Is>(ipack<sorted_pack>{})), decltype(ipack_element<Is + 1>(ipack<sorted_pack>{})) >::value... }};
}
static constexpr std::size_t unique_count() {
constexpr std::array<bool, sizeof...(Ts)> const ut = unique_types();
std::size_t result = 0;
for (std::size_t i = 0; i < sizeof...(Ts); i++)
result += ut[i];
return result;
}
template <std::size_t... Is2>
static constexpr std::array<std::size_t, unique_count()> const unique_idxs(std::index_sequence<Is2...>) {
std::size_t result[unique_count()] {};
std::size_t cur = 0;
constexpr std::array<bool, sizeof...(Ts)> const ut = unique_types();
for (std::size_t i = 0; i < sizeof...(Ts); i++) {
if (ut[i])
result[cur++] = i;
}
return {{ result[Is2]... }};
}
template <std::size_t... Is2>
static constexpr std::array<std::size_t, unique_count()> const unique_counts(std::index_sequence<Is2...>) {
std::size_t result[unique_count()] {};
std::size_t cur = 0;
constexpr auto ut = unique_types();
for (std::size_t i = 0; i < sizeof...(Ts); i++) {
if (ut[i])
result[cur++]++;
else
result[cur]++;
}
return {{ result[Is2]... }};
}
template <std::size_t... Is2>
static auto make_type(std::index_sequence<Is2...>) {
constexpr std::array<std::size_t, unique_count()> const idxs = unique_idxs(std::index_sequence<Is2...>{});
constexpr std::array<std::size_t, unique_count()> const counts = unique_counts(std::index_sequence<Is2...>{});
return unique_pack<itag<counts[Is2], decltype(ipack_element<idxs[Is2]>(ipack<sorted_pack>{}))>...>{};
}
template <class T = multiset, std::size_t UC = T::unique_count()>
using type = decltype(make_type(std::make_index_sequence<UC>{}));
};
template <class P1, class P2>
constexpr bool multiset_equality(P1, P2) {
return typename multiset<P1>::template type<>{} == typename multiset<P2>::template type<>{} && typename multiset<P2>::template type<>{} == typename multiset<P1>::template type<>{};
}
int main() {
static_assert(multiset_equality(pack<char, int, double, float, int, float>{}, pack<float, char, int, double, int, float>{}),"!");
static_assert(!multiset_equality(pack<char, int, double, float, int, float>{}, pack<char, int, double, int, float>{}),"!");
static_assert(multiset_equality(pack<char, char, char, float, float, float>{}, pack<char, float, char, float, char, float>{}),"!");
static_assert(!multiset_equality(pack<int, int>{}, pack<int, int, int>{}),"!");
}
[live demo]
Interesting question...
Taking inspiration from your answer (well... copying it) in the original question (the type_set_eq one), adding a type counter (countT) and removing the helper struct and the tag struct, I suppose that you can simply write something as follows
#include <tuple>
#include <type_traits>
template <typename ...>
struct countT;
template <typename T>
struct countT<T>
{ static constexpr std::size_t value { 0U }; };
template <typename T, typename T0, typename ... Ts>
struct countT<T, T0, Ts...>
{ static constexpr std::size_t value { countT<T, Ts...>::value }; };
template <typename T, typename ... Ts>
struct countT<T, T, Ts...>
{ static constexpr std::size_t value { 1U + countT<T, Ts...>::value }; };
template <bool ...>
struct bool_pack
{ };
template <bool ... Bs>
using my_and = std::is_same<bool_pack<Bs..., true>, bool_pack<true, Bs...>>;
template <typename, typename, typename = void>
struct type_multiset_eq : std::false_type
{ };
template <template <typename ...> class C1, typename ... Ts1,
template <typename ...> class C2, typename ... Ts2>
struct type_multiset_eq<C1<Ts1...>, C2<Ts2...>,
typename std::enable_if<
(sizeof...(Ts1) == sizeof...(Ts2))
&& (my_and<( countT<Ts1, Ts1...>::value
== countT<Ts1, Ts2...>::value)...>::value)
>::type>
: std::true_type
{ };
int main()
{
static_assert( type_multiset_eq<
std::tuple<char, int, double, float, int, float>,
std::tuple<float, char, int, double, int, float>>::value, "err");
static_assert( ! type_multiset_eq<
std::tuple<char, int, double, float, int, float>,
std::tuple<char, int, double, int, float>>::value, "err");
static_assert( type_multiset_eq<
std::tuple<char, char, char, float, float, float>,
std::tuple<char, float, char, float, char, float>>::value, "err");
static_assert( ! type_multiset_eq<
std::tuple<int, int>,
std::tuple<int, int, int>>::value, "err");
}
In case you can use C++14, you can substitute the countT type trait with the following constexpr function
template <typename T, typename ... Ts>
constexpr std::size_t cntT ()
{
using unused = std::size_t[];
std::size_t ret { 0U };
(void)unused { 0U, ret += (std::is_same<T, Ts>::value ? 1U : 0U)... };
return ret;
}
You can use the following:
it removes from both side type until first is empty, or second doesn't match:
template <typename T, typename Tuple, typename Res = std::tuple<>>
struct remove_type_from_tuple;
template <typename T, typename ... Ts, typename ...Res>
struct remove_type_from_tuple<T, std::tuple<T, Ts...>, std::tuple<Res...>>
{
using type = std::tuple<Res..., Ts...>;
};
template <typename T, typename T2, typename ... Ts, typename ...Res>
struct remove_type_from_tuple<T, std::tuple<T2, Ts...>, std::tuple<Res...>>
{
using type = typename remove_type_from_tuple<T,
std::tuple<Ts...>,
std::tuple<Res..., T2>>::type;
};
template <typename T, typename Res>
struct remove_type_from_tuple<T, std::tuple<>, Res>
{
using type = void;
};
template <typename T, typename Res>
struct remove_type_from_tuple<T, void, Res>
{
using type = void;
};
template <typename Tuple1, typename Tuple2>
struct diff_types_from_tuple;
template <typename T, typename ...Ts, typename Tuple>
struct diff_types_from_tuple<std::tuple<T, Ts...>, Tuple>
{
using type =
typename diff_types_from_tuple<std::tuple<Ts...>,
typename remove_type_from_tuple<T, Tuple>::type
>::type;
};
template <typename Tuple>
struct diff_types_from_tuple<std::tuple<>, Tuple>
{
using type = Tuple;
};
template <typename Tuple1, typename Tuple2>
struct type_multiset_eq :
std::is_same<std::tuple<>,
typename diff_types_from_tuple<Tuple1, Tuple2>::type>
{
};
Demo
Just for fun, I propose another solution based on type counting (like the first one) with the same complexity (O(n^2), I suppose) but a little smarter (end the check at the first difference)
#include <tuple>
#include <type_traits>
template <typename ...>
struct countT;
template <typename T>
struct countT<T>
{ static constexpr std::size_t value { 0U }; };
template <typename T, typename T0, typename ... Ts>
struct countT<T, T0, Ts...>
{ static constexpr std::size_t value { countT<T, Ts...>::value }; };
template <typename T, typename ... Ts>
struct countT<T, T, Ts...>
{ static constexpr std::size_t value { 1U + countT<T, Ts...>::value }; };
template <typename, typename, typename>
struct eqCountT;
template <template <typename ...> class C, typename T, typename ... Ts1,
typename ... Ts2, typename ... Ts3>
struct eqCountT<C<T, Ts1...>, C<Ts2...>, C<Ts3...>>
: std::integral_constant<bool,
(countT<T, Ts2...>::value == countT<T, Ts3...>::value)>
{ };
template <template <typename ...> class C, typename T2, typename T3>
struct eqCountT<C<>, T2, T3> : std::true_type
{ };
template <typename T1, typename T2, typename T3,
bool = eqCountT<T1, T2, T3>::value>
struct mseqH;
template <template <typename ...> class C, typename T2, typename T3>
struct mseqH<C<>, T2, T3, true> : std::true_type
{ };
template <typename T1, typename T2, typename T3>
struct mseqH<T1, T2, T3, false> : std::false_type
{ };
template <template <typename ...> class C, typename T, typename ... Ts1,
typename T2, typename T3>
struct mseqH<C<T, Ts1...>, T2, T3, true> : mseqH<C<Ts1...>, T2, T3>
{ };
template <typename, typename>
struct type_multiset_eq;
template <template <typename ...> class C1, typename ... Ts1,
template <typename ...> class C2, typename ... Ts2>
struct type_multiset_eq<C1<Ts1...>, C2<Ts2...>>
: std::integral_constant<bool,
(sizeof...(Ts1) == sizeof...(Ts2))
&& mseqH<C1<Ts1...>, C1<Ts1...>, C1<Ts2...>>::value>
{ };
int main()
{
static_assert( type_multiset_eq<
std::tuple<char, int, double, float, int, float>,
std::tuple<float, char, int, double, int, float>>::value, "err");
static_assert( ! type_multiset_eq<
std::tuple<char, int, double, float, int, float>,
std::tuple<char, int, double, int, float>>::value, "err");
static_assert( type_multiset_eq<
std::tuple<char, char, char, float, float, float>,
std::tuple<char, float, char, float, char, float>>::value, "err");
static_assert( ! type_multiset_eq<
std::tuple<int, int>,
std::tuple<int, int, int>>::value, "err");
}
In case you can use C++14, you can substitute the countT type trait with the following constexpr function
template <typename T, typename ... Ts>
constexpr std::size_t cntT ()
{
using unused = std::size_t[];
std::size_t ret { 0U };
(void)unused { 0U, ret += (std::is_same<T, Ts>::value ? 1U : 0U)... };
return ret;
}