2

It's possible to iterate over a boost or std tuple, but can I iterate in an order determined at runtime, while still retaining type information?

Suppose my tuple was filled with objects of type Foo:

#include <tuple>

using namespace std;

template <typename ...> void bar(); // Definition omitted.

template <typename ... Ts>
struct Foo {
  void doit() { bar<Ts...>(); }
  int rank;
};

int main(int argc, char *argv[])
{
  auto tup = make_tuple(Foo<int,double>(),
                        Foo<bool,char,float>());
  get<0>(tup).rank = 2;
  get<1>(tup).rank = 1;
  return 0;
}

I would like to be able to traverse the list of Foo types, calling their doit methods, but in an arbitrary order defined by, say, the value of the rank member.

user2023370
  • 10,488
  • 6
  • 50
  • 83

1 Answers1

3

You will need to implement some type erasure in order to make this happen. Something along the lines of

template <typename ...> void bar(); // Definition omitted.

struct FooBase {
    virtual void doit() = 0;
    int rank;
};

template <typename ... Ts>
struct Foo : public FooBase {
  void doit() { bar<Ts...>(); }
};

int main(int argc, char *argv[])
{
  auto tup = make_tuple(Foo<int,double>(),
                        Foo<bool,char,float>());
  get<0>(tup).rank = 2;
  get<1>(tup).rank = 1;
  std::vector<FooBase*> bases;
  // fill bases
  // sort
  // call
  return 0;
}

There are other mechanisms you can apply which are functional, for example, and do not require modifying Foo, but they all boil down to the same principle- type erasure. I merely provided the simplest implementation of that erasure.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • Thankyou. I'm not immediately clear on how the "call" phase would work: after I sort `bases`, I have a `vector` of `(FooBase *)` but I need the full type to call `doit()`, no? I also have humble concerns about the portability of virtual functions, and would be very interested in the functional approach you mention. – user2023370 Jan 18 '12 at 23:15
  • @user643722: `virtual` functions are completely and totally portable, *vastly* more so than the template machinery powering `bar`. You need the full type to call `doit()`, but that type is "erased" by the virtual function and then known again in the actual `doit()` body. – Puppy Jan 19 '12 at 00:04
  • Thanks again. I can make a vector of `(FooBase*)`, I expect `bases.push_back((FooBase*)&get<0>(tup));` would be a start. But, how can I call the `doit()` method once the ordering of `bases` has been altered: I don't know the type of each `(FooBase *)` pointer. – user2023370 Jan 19 '12 at 10:58
  • @user643722: Because it's a `virtual` method? The whole reason this works is because *you don't have to know* the derived type. It is abstracted at run-time. – Puppy Jan 19 '12 at 13:46
  • If I make a call like `bases[0].doit();`, I get: `error: request for member 'doit' in` ... `which is of non-class type 'FooBase*'` – user2023370 Jan 19 '12 at 14:20
  • 1
    @user643722: Because `FooBase*` is a pointer, so you use `bases[0]->doit();`. – Puppy Jan 20 '12 at 01:20
  • Oops, of course it is, sorry. I was so convinced that it wouldn't work; but it does. I clearly need to revise my knowledge of virtual methods. (Too much time with Haskell lately.) I will still seek a functional solution. – user2023370 Jan 21 '12 at 11:15