2

I am new to meta programming. I have looked at other questions that are similar but none of them do what I really want.

Here is my attempt at inversing a std::tuple. The main issue i have is inverting the data in the input tuple.

The logic to inverse the indices is not palatable and I could not proceed from this stage.

The code so far:

//===========================================================!
// type inversion of a tuple

template < typename Tuple, typename T >
struct tuple_push;

template < typename T, typename ... Args >
struct tuple_push<std::tuple<Args...>, T>
{
    typedef std::tuple<Args..., T> type;
};

template < typename Tuple >
struct tuple_reverse;

template < typename T, typename ... Args >
struct tuple_reverse<std::tuple<T, Args...>>
{
    typedef typename tuple_push<typename tuple_reverse<std::tuple<Args...>>::type, T>::type type;
};

template < >
struct tuple_reverse<std::tuple<>>
{
    typedef std::tuple<> type;
};
//===========================================================!


template <typename First, typename ...Tails>
auto inverse(std::tuple<First, Tails...> & data) 
-> decltype(tuple_reverse<std::tuple<First,Tails...>>::type)
    {
        using reverse_tup = tuple_reverse<std::tuple<First, Tails...>>::type;
        static_assert(false, "Confused!")
        return reverse_tup();
    }

Looking forward to a compact and simple solution.

Ram
  • 3,045
  • 3
  • 27
  • 42

2 Answers2

7

This is a possible solution using C++14:

template <typename T, std::size_t... indices>
auto invert(T &&tuple, std::index_sequence<indices...>) {
  // Using decay_t as the argument must be a tuple, and this shortens the code
  using tuple_t = std::decay_t<T>;
  constexpr auto tuple_size = std::tuple_size<tuple_t>{};
  return std::tuple<std::tuple_element_t<tuple_size - indices - 1, tuple_t>...>(
      std::get<tuple_size - indices - 1>(std::forward<T>(tuple))...);
}

template <typename T>
auto invert(T &&tuple) {
  return invert(std::forward<T>(tuple),
                std::make_index_sequence<std::tuple_size<std::decay_t<T>>{}>());
}

Demo. For C++11, the same procedure is possible, but helper templates like make_index_list have to be provided.

bolov
  • 72,283
  • 15
  • 145
  • 224
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Can't find a good formatting for the last line in the first overload. Any ideas? – Columbo Jan 30 '15 at 17:09
  • I'd `using ret_type = std::tuple...>;` on one line, then do the `std::get` on another. I believe the above will turn `std::array`s into backward `tuple`s? – Yakk - Adam Nevraumont Jan 30 '15 at 18:11
  • @Yakk Quite correct ;D I'll fix that, thanks. (Actually, should I correct it? I mean, it would be quite tedious to extract the template, wouldn't it) – Columbo Jan 30 '15 at 18:16
  • Well, I went down this pretty nice road, and [this is where I ended up](http://coliru.stacked-crooked.com/a/2779756fa81a5eb1). I do not know if I should be horrified. – Yakk - Adam Nevraumont Jan 30 '15 at 18:20
  • @Yakk I don't get it. Your template returns tuples when given arrays, or did I miss sth.? – Columbo Jan 30 '15 at 18:21
  • Oh, your code returns tuples when given arrays. Mine behaves exactly like yours. [try this main on your code](http://coliru.stacked-crooked.com/a/13c7730c00f8adaf). Arrays and pairs are tuple-like, so your code consumes them, but the code always outputs a tuple. A slightly more sophisticated solution could keep the template type of the input somehow (not sure how?) -- doing it for `pair` is easy, but `array` is messy. For the above, a simple `using ret_type=ret_type=std::tuple...>;` on the line before `return`, then `return ret_type(` – Yakk - Adam Nevraumont Jan 30 '15 at 18:24
1

The logic to inverse the indices is not palatable and I could not proceed from this stage.

I take it you are referring to solutions that use std::index_sequence (C++14), or the like, such as Jonathan Wakeley's', and that @Columbo's solution in the same vein would be unpalatable too, even if it it was a C++11 one.

Possibly you dislike "the logic to inverse the indices" because you think it has an unecessary runtime cost. It has no runtime cost. It's executed at compiletime.

More likely, you know that but just think that this style of solution is inelegant, not as simple or compact as you'd prefer.

Well, you know this classic recursive algorithm for reversing a sequence S: Take the items of S successively and push them to the front of another, initially empty sequence S'. At the end, S' is the reverse of S.

There could hardly be anything simpler, and here is a compact C++11 implementation, as applied to tuples. The header "tuple_plus.h" is assumed to provide your existing definition of the meta-function tuple_reverse and its prerequisites.

#include "tuple_plus.h"

namespace detail {

template <size_t I, class Wip, typename ...Ts>
Wip
inverse(std::tuple<Ts...> const & model, Wip && wip = std::tuple<>(),
            typename std::enable_if<(I >= sizeof...(Ts))>::type * = nullptr)
{
    return wip;
}

template <size_t I, class Wip, typename ...Ts>
typename tuple_reverse<std::tuple<Ts...>>::type
inverse(std::tuple<Ts...> const & model, Wip && wip = std::tuple<>(),
            typename std::enable_if<(I < sizeof...(Ts))>::type * = nullptr)
{
    return
    inverse<I + 1>(model,std::tuple_cat(std::make_tuple(std::get<I>(model)),wip));
}

} // namespace detail

template<typename ...Ts>
typename tuple_reverse<std::tuple<Ts...>>::type
inverse(std::tuple<Ts...> const & tup)
{
    return detail::inverse<0,std::tuple<>>(tup);
}

Of course we can't just loop over tuple elements, because they can only be accessed via std::get<I>(tup), for constant index I. So the implementation can't be as compact as it may, say, for std::vector<T>. We have to follow the usual pattern of template-meta-recursion on index constants. We need a pair of SFINAE overloads, one getting selected by the compiler when I has the limit value (I >= sizeof...(Ts)) and the other when I has any other value (I < sizeof...(Ts)). And then we need to implement the function template that you actually want

template<typename ...Ts>
typename tuple_reverse<std::tuple<Ts...>>::type
inverse(std::tuple<Ts...> const & tup);

as a wrapper round this implementation to initialize and conceal the recursion parameters. But with these unavoidable arrangements, this is the classic sequence-reversing algorithm, applied to tuples in C++11, without any index_sequence crutches.

N.B. That declaration differs slightly from the one in your post:-

  • It will accept a 0-length tuple, std::tuple<>, as well as longer tuples. This is better because your generic definition of the return-type tuple_reverse<Tuple>::type is specialized for std::tuple<>. So this way the function is defined whenever the return-type is.

  • The argument is passed as const &, not just &. You are not going to modify the input tuple, so the function should accept std::tuple<Ts...> const arguments.

Now I'll explain why this elegant solution is a non-starter, compared with the sort that rests on index_sequence apparatus.

Here's a program with which you can test a tuple-reversing solution that fulfils the desired interface.

#include "tuple_plus.h"
#include <type_traits>

///////////////////////////////////////////////////
//
// Paste your implementation here
//
///////////////////////////////////////////////////

///////////////////////////////////////////////////
// Testing it...

#include <iostream>

using namespace std;

struct item
{
    static unsigned ctor_count;
    static unsigned copy_count;
    item()
    : id(ctor_count++) {}
    item(item const & other)
    : id(other.id) {
        ++copy_count;
    }
    item & operator=(item const & other) {
        id = other.id;
        ++copy_count;
        return *this;
    }
    static void clear() {
        ctor_count = copy_count = 0;
    }
    unsigned id;
};

unsigned item::ctor_count;
unsigned item::copy_count;

ostream & operator<<(ostream & out, item const & e)
{
    return out << "item #" << e.id;
}

template<unsigned I = 0, typename ...Ts>
typename enable_if<(I >= sizeof...(Ts))>::type
tuple_print(tuple<Ts...> const &)
{
    cout << "\n";
}

template<unsigned I = 0, typename ...Ts>
typename enable_if<(I < sizeof...(Ts))>::type
tuple_print(tuple<Ts...> const & tup)
{
    cout << '[' << get<I>(tup) << "] ";
    tuple_print<I + 1>(tup);
}

template<unsigned I>
void test()
{
    auto tup_tup =
    tuple<
        tuple<>,
        tuple<item>,
        tuple<item,item>,
        tuple<item,item,item>,
        tuple<item,item,item,item>,
        tuple<item,item,item,item,item>,
        tuple<item,item,item,item,item,item>>();

    item::clear();
    auto const & tup = get<I>(tup_tup);
    cout << "In..." << endl;
    tuple_print(tup);
    cout << "Out..." << endl;
    tuple_print(inverse(tup));
    cout << "To invert a " << I << "-element tuple\n"
    << "I copied " << item::copy_count << " elements\n";
}

int main()
{
    test<0>(),test<1>(),test<2>(),test<3>(),test<4>(),test<5>(),test<6>();
    return 0;
}

Cut and paste the classic solution at:

    // Paste your implementation here

Then compile, with -std=c++11 and run. The output exercises the classic algorithm for specimen tuples of length 0 to 6, and shows you in each case a) that the algorithm does indeed reverse the tuple and b) the cost of reversing that tuple, measured in tuple-element copy operatons.

In...

Out...

To invert a 0-element tuple
I copied 0 elements
In...
[item #20]
Out...
[item #20]
To invert a 1-element tuple
I copied 3 elements
In...
[item #19] [item #18]
Out...
[item #18] [item #19]
To invert a 2-element tuple
I copied 7 elements
In...
[item #17] [item #16] [item #15]
Out...
[item #15] [item #16] [item #17]
To invert a 3-element tuple
I copied 12 elements
In...
[item #14] [item #13] [item #12] [item #11]
Out...
[item #11] [item #12] [item #13] [item #14]
To invert a 4-element tuple
I copied 18 elements
In...
[item #10] [item #9] [item #8] [item #7] [item #6]
Out...
[item #6] [item #7] [item #8] [item #9] [item #10]
To invert a 5-element tuple
I copied 25 elements
In...
[item #5] [item #4] [item #3] [item #2] [item #1] [item #0]
Out...
[item #0] [item #1] [item #2] [item #3] [item #4] [item #5]
To invert a 6-element tuple
I copied 33 elements

Investigate the sequence of costs:

0, 3, 7, 12, 18, 25, 33,...

and you can satisfy yourself that it is given by the function:

Cost(n) = (n squared + 5n) / 2

So this algorithm is quadratically costly with tuple-size.

If you want the exercise, you can figure out an implementation, for tuples, of another stock recursive algorithm for reversing a sequence. Not as simple or compact as this one, it's the one that goes: Swap the first and last elements of the sequence, after you have already reversed the sequence in between. The cost may well be lower, but it will still be quadratic.

Which is distressing, because it's well known that reversing a sequence of things is a linear problem: for instance, an obvious C++ adaptation of the classic algorithm to reversing an std::vector would have Cost(n) = 4n.

The implicit assumption behind that well known fact is that the sequence is a Container<T>, for some Container and T; so a reversing algorithm is free to modify the contained sequence at a position while not modifying it at others, all the while still having a Container<T>.

But the trouble with reversing an std::tuple is that an std::tuple<....,A,....,B,....> is an thing of a different type from an std::tuple<....,B,....,A,....>, and from an std::tuple<....,A,....>. So you can't actually reverse an std::tuple tup by means of pushing another element on the front of tup; or swapping the A and B in tup, or the like. To simulate any such member-wise operation on tup you have to construct a whole new tuple, of a different type, copying all the elements of tup, and maybe more. To pick up the banana you have to lift the whole gorilla.

With that in mind, paste @Columbo's implementation into the test program. You'll need to change invert in his code to inverse. Compile with std=c++14 and run. (You'll need gcc 4.9 or clang 3.5 for this option, and gcc 4.9 will emit an unimportant compiler-warning).

Now the output is:

In...

Out...

To invert a 0-element tuple
I copied 0 elements
In...
[item #20]
Out...
[item #20]
To invert a 1-element tuple
I copied 1 elements
In...
[item #19] [item #18]
Out...
[item #18] [item #19]
To invert a 2-element tuple
I copied 2 elements
In...
[item #17] [item #16] [item #15]
Out...
[item #15] [item #16] [item #17]
To invert a 3-element tuple
I copied 3 elements
In...
[item #14] [item #13] [item #12] [item #11]
Out...
[item #11] [item #12] [item #13] [item #14]
To invert a 4-element tuple
I copied 4 elements
In...
[item #10] [item #9] [item #8] [item #7] [item #6]
Out...
[item #6] [item #7] [item #8] [item #9] [item #10]
To invert a 5-element tuple
I copied 5 elements
In...
[item #5] [item #4] [item #3] [item #2] [item #1] [item #0]
Out...
[item #0] [item #1] [item #2] [item #3] [item #4] [item #5]
To invert a 6-element tuple
I copied 6 elements

The sequence of costs is 0,1,2,3,4,5,6,.... For this solution, Cost(n) = n: it is optimally efficient.

The use of an index_sequence apparatus is in this solution is the essense of its optimal efficiency. If you are to avoid the quadratic runtime cost of reversing tup by simulated member-wise operations, the only alternative is simply to construct the reversed tuple, initialized with elements that are already mapped, at compiletime, from their counterparts in tup, by the mapping:

Index I -> tuple_size - I - 1

At runtime, nothing happens but the construction of the reversed tuple, finished as soon it exists.

But to execute that mapping, at compiletime, you have got to possess a list of the indices to the elements of tup, at compiletime. That is what the index_sequence apparatus delivers.

If C++14 is absolutely off-limits, you might accept a flyweight C++11 substitute for std::index_sequence courtesty of Andy Prowl

With this, you could have the following solution:

namespace detail {

// This is...

template<int... Is>
struct seq { };

template<int N, int... Is>
struct gen_seq : gen_seq<N - 1, N - 1, Is...> { };

template<int... Is>
struct gen_seq<0, Is...> : seq<Is...> {};

// ... the flyweight index_sequence apparatus.
// You can put it in a header.

template<typename Tuple, int ...Is>
typename tuple_reverse<typename std::decay<Tuple>::type>::type
invert(Tuple && tup, seq<Is...>)
{
    return
        std::make_tuple(
            std::get<std::tuple_size<
                typename std::decay<Tuple>::type>::value - Is - 1>
                    (std::forward<Tuple>(tup))...);
}

} //namespace detail

template<typename ...Ts>
typename tuple_reverse<std::tuple<Ts...>>::type
inverse(std::tuple<Ts...> const & tup)
{
    return detail::invert(tup,detail::gen_seq<(sizeof...(Ts))>());
}

In the test program, its output is identical to @Columbo's.

Moral: std::index_sequence (or more generally, std::integer_sequence) is a superbly elegant and fundamental tool for effective TMP in C++. That's why it's in the Standard Library as of C++14. (BTW, Jonathan Wakeley is its author`)

Community
  • 1
  • 1
Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182