3

I have two tuples, each containing containers of different types.

std::tuple<containerA<typesA>...> tupleA;
std::tuple<containerB<typesB>...> tupleB;

So, as an example tupleA might be defined like this:

std::tuple<list<int>, list<float>> tupleA;

The two containers, containerA and containerB are different types. typesA and typesB do not intersect. I want to sort the containers inside the tuples by their sizes, and be able to access them by their type. So, as an example

std::tuple<list<int>, list<float>> tupleA {{2}, {3.3f, 4.2f}};
std::tuple<deque<double>, deque<uint8_t>> tupleB {{2.0, 1.2, 4.4}, {}};

auto sortedArray = sort(tupleA, tupleB);
sortedArray == {deque<uint8_t>, list<int>, list<float>, deque<double>};
sortedArray.get<list<float>>() == {3.3f, 4.2f};
std::get<list<int>>(tupleA).push_back(4);
std::get<list<int>>(tupleA).push_back(5);
std::get<list<int>>(tupleA).push_back(6);
sortedArray = sort(tupleA, tupleB);
sortedArray == {deque<uint8_t>, list<float>, deque<double>, list<int>};

The most important part is that I have to store sortedArray and that the sizes of the elements in the tuple may change. I have not been able to create such a sort function. The actual syntax of accessing the sortedArray is not important.

I tried to use naive indexes to the container data inside the sortedArray, such as using std::pair<A, 1> to refer to the second element in tuple A, however I can't access the information of the tuple through it because it is not a constexpr

user975989
  • 2,578
  • 1
  • 20
  • 38
  • I'm not sure this is entirely possible because a tuple is a type, which must be known at compile time, while the containers within the tuple are not constexpr containers, and thus their elements will not be known until runtime. – AndyG Jun 22 '16 at 15:38
  • 3
    @ArchbishopOfBanterbury You can't `std::sort` a tuple, because their order is immutable. To do so would create a new type. – user975989 Jun 22 '16 at 15:44
  • Ah crap yeah, what am I talking about, never mind then. – sjrowlinson Jun 22 '16 at 15:45
  • If your sorted container did some serious type erasure on the containers held within, then they could move around at runtime, I suppose (like all the way down to `void*`). They'd have to "quack" nicely, though. – AndyG Jun 22 '16 at 15:47
  • @AndyG I was thinking about that too, but how would I cast them back to their real types? – user975989 Jun 22 '16 at 15:49
  • 2
    You say "I want to sort the containers inside tuples by their sizes." What do you mean by "sizes"? The number of elements they contain? If so that's run time information and the ordering of types in a `tuple` is type information and thereby required at compile time. – Jonathan Mee Jun 22 '16 at 15:52
  • 1
    @JonathanMee Yes, by the number of elements they contain. I don't mean the ordering of the actual `tuple`s, I mean the ordering of the `sortedArray` I create. This `sortedArray` can be a `vector` or whatever type you need it to be. – user975989 Jun 22 '16 at 15:55
  • A `vector` has a singular type. You are suggesting that it contain elements of different types? That would not be possible.An alternative would be to use a `tuple` to contain your other containers, but you'd need to set up the order at compile time. Which seems to be what you're trying to avoid. – Jonathan Mee Jun 22 '16 at 16:04
  • If you have tried to write the function signature, you would have seen the problem runtime/compile time. – Jarod42 Jun 22 '16 at 16:20
  • 3
    I'm curious as to how you arrived at this requirement. – Lightness Races in Orbit Jun 22 '16 at 16:21
  • 3
    @LightnessRacesinOrbit I agree. Someone will undoubtedly suggest this be handled with `void*` or some other method for skirting the rules of the language. But this problem *fights against* the language itself. The best solution would involve using the power of templates and *working with* the power of the language. But to find such a solution more information on the motivation of the requirement will be needed. – Jonathan Mee Jun 22 '16 at 16:31
  • @user975989: You'd have to attach some kind of type information object to these containers, and then perform a bunch of gross if..else if checking (dynamic dispatch), followed by a cast. – AndyG Jun 22 '16 at 17:47
  • @AndyG Which as I mention in my comment, "fights against the language". We need more information so we can come up with a solution that works with the language. – Jonathan Mee Jun 22 '16 at 17:58

2 Answers2

1

This is possible but not easy.

First, you need to generate a compile-time list of every permutation of N elements (there are N factorial of them).

Write both a runtime and compile time permutation object (separate).

Make a runtime map from permutation to index. (map step)

Convert your tuple to a vector of (index, size). Sort this by size. Extract the permutation. Map this to the index of the set of permutations. (Sort step, uses map step)

Write a "magic switch" that takes a function object f and a permutation index, and invokes the f with the compile time permutation. (magic step)

Write code that takes a compile time permutation and reorders a tuple based on it. (reorder step)

Write code that takes a function object f and a tuple. Do (sort step). Do (magic step), feeding it a second function object g that takes the passed in permutation and the tuple and (reorder step), then calls f with it.

Call the function that does this bob.

std::tuple<list<int>, list<float>> tupleA {{2}, {3.3f, 4.2f}};
std::tuple<deque<double>, deque<uint8_t>> tupleB {{2.0, 1.2, 4.4}, {}};

bob(concat_tuple_tie(tupleA, tupleB), [&](auto&& sorted_array){
  assert( std::is_same<
    std::tuple<deque<uint8_t>&, list<int>&, list<float>&, deque<double>&>,
    std::decay_t<decltype(sorted_array)>
  >::value, "wrong order" );
  sortedArray.get<list<float>>() == {3.3f, 4.2f};
});
std::get<list<int>>(tupleA).push_back(4);
std::get<list<int>>(tupleA).push_back(5);
std::get<list<int>>(tupleA).push_back(6);
bob(concat_tuple_tie(tupleA, tupleB), [&](auto&& sorted_array){
  assert( std::is_same<
    std::tuple<deque<uint8_t>&, list<float>&, deque<double>&, list<int>&>,
    std::decay_t<decltype(sorted_array)>
  >::value, "wrong order" );
});

Personally, I doubt you need to do this.

I could do this, but it might take me hours, so I'm not going to do it for a SO answer. You can look at my magic switch code for the most magic of the above. The other hard part is that permutation step.

Note that the code uses continuation passing style. Also note that every permutation of the lambda is instantiated, including the wrong ones, so your code must be valid for every permutation. This can result in an insane amount of code bloat.

An alternative solution could involve creating type-erasure wrappers around your containers and simply sorting those, but that is not what you asked for.

Community
  • 1
  • 1
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

The problem you describe sounds very much like you want dynamic polymorphism, not static polymorphism — and your difficulty in solving it is because you're stuck on trying to express with tools for doing static polymorphism.

i.e. you need a type (hierarchy) for collections (or pointers to collections) that let you select the type of collection and the type of data at run-time, but still provides a unified interface to functions you need like size (e.g. by inheritance and virtual functions).

e.g. a beginning of a sketch might look like

struct SequencePointer
{
    virtual size_t size() const = 0;
    virtual boost::any operator[](size_t n) const = 0;
};

template <typename Sequence>
struct SLSequencePointer
{
    const Sequence *ptr;
    size_t size() const { return ptr->size(); }
    boost::any operator[](size_t n) const { return (*ptr)[n]; }
};