2

I am trying to implement a vector that can take elements of several types, and can apply a function on all of them. This is easily done with a base class, virtual functions and inheritance, but I explicity do not want to use it. Here is how far I am so far:

#include <iostream>
#include <vector>
#include <tuple>

// this will be my new polymorphic vector;
template<typename... Ts>
class myvector {
    std::tuple<std::vector<Ts>...> vectors;

    template <template<typename> class funtype>
    void for_each() {
    }

    template <template<typename> class funtype, typename X, typename... Xs>
    void for_each() {
        std::vector<X>& vector = std::get<std::vector<X>>(vectors);
        for ( X& x : vector ) {
            funtype<X> fun;
            fun(x);
        }
        for_each<funtype, Xs...>();
    }

public:
    template <typename T>
    void push_back(const T& t) {
        std::vector<T>& vector = std::get<std::vector<T>>(vectors);
        vector.push_back(t);
    }

    template <typename T>
    void pop_back() {
        std::vector<T>& vector = std::get<std::vector<T>>(vectors);
        vector.pop_back();
    }

    /* here I would like to pass a function, or function object that 
     * can be expanded to all underlying types. I would prefer to just 
     * give a function name, that has an implementation to all types in Ts
     */
    template <template<typename> class funtype>
    void ForEach() {
        for_each<funtype,Ts...>();
    }
};


struct foo {
};

struct bar {
};

template <typename T>
void method(T& t);

template<>
void method(foo& b) {
    std::cout << "foo" << std::endl;
}

template<>
void method(bar& b) {
    std::cout << "bar" << std::endl;
}

int main()
{
    myvector<foo,bar> mv;
    mv.push_back( foo{} );
    mv.push_back( bar{} );
    mv.ForEach<method>();
}

at the moment I am kind of stuck, I hope you can give me some advise on how to go further.

Constructor
  • 7,273
  • 2
  • 24
  • 66
Arne
  • 7,921
  • 9
  • 48
  • 66
  • 3
    First off, you should add the tag for the language you're using (pretty sure it's c++ but I don't want to assume). Second, explain why you *explicitly* don't want to use the constructs the language gives you to solve this kind of problem? Perhaps because the homework assignment specifically instructs you not to? – RyanR Aug 07 '14 at 19:41
  • thanks for the c++ hint. Am am shure it was there and secretly disappered. And second: no it's not a homework. It's just because I don't like pointers in any form in c++. I like to keep everything flat data. Also sorting by type is generally a good thing for performance. – Arne Aug 07 '14 at 19:47
  • What is your `ForEach` function intended to? Operate on all objects stored? In which order? – dyp Aug 07 '14 at 19:51
  • yes operate on all objects stored, the order doesn't matter. – Arne Aug 07 '14 at 19:53
  • You probably want something like `template void for_each(Fun&& fun);` for the single-type version, and `template void ForEach(Fun&& fun) { int dummy[] = { (for_each(fun), 0)... }; (void)dummy; };` for the all-types version. This is intended to be used with a function object that has a set of overloaded `operator()`. – dyp Aug 07 '14 at 19:54
  • *"I would prefer to just give a function name"* In C++1y, you can pass a polymorphic lambda: `[](auto&&... pp) -> decltype(auto) { return method( std::forward(pp)... ); }` – dyp Aug 07 '14 at 20:00
  • The easiest option is to require `funtype` to have a suitable `operator()` overload for every `T`. That's a very standard well-known function name. Passing a lambda as above will allow the called to customize if necessary. – Useless Aug 07 '14 at 20:00
  • @Useless can you pleas tell me how the template parameter list looks if you do something like that. because that's what I would like to do, but i'm not smart enough to do that now. – Arne Aug 07 '14 at 20:06
  • @dyp thatk you for your hint, but I am still a little bit confused with the c++ template syntax, so could you make it as an answer? Because right now I don't know what you mean. Also why should I use an rvalue reference? I've learned to only use then for the mover constructor, everything else makes things too complicated – Arne Aug 07 '14 at 20:09
  • In a function template with a template type-parameter `T`, a function parameter `T&&` is not (really) an rvalue-reference, but a [universal reference](http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers). – dyp Aug 07 '14 at 20:24
  • `boost::variant` may be of interest. – Yakk - Adam Nevraumont Aug 07 '14 at 20:39

1 Answers1

3

A common solution is to use a function object with a set of operator():

struct my_fun_type
{
    void operator()(foo&) const
    { std::cout << "foo\n"; }

    void operator()(bar&) const
    { std::cout << "bar\n"; }
};

This allows to pass a "set" of overloaded functions to an algorithm, state, and is rather convenient to use:

my_algorithm(my_fun_type{});

If we want to add support for such function objects, we could define ForEach as follows:

template <typename Elem, typename Fun>
void for_each(Fun&& fun) {
    std::vector<Elem>& vector = std::get<std::vector<Elem>>(vectors);
    for ( Elem& e : vector ) {
        fun(x);
    }
}

template <typename Fun>
void ForEach(Fun&& fun) {
    int dummy[] = { 0, (for_each<Ts>(fun), 0)... };
    (void)dummy;
}

That dummy is a trick to call for_each for all types in Ts. The (void)dummy is intended to suppress a compiler warning (dummy is never read from).

You can learn more about this technique in other Q&As, such as that one.

The Fun&& is not an rvalue reference, but a universal reference.


Note that the above example differs from many Standard Library algorithms, which take the function object by value:

template <typename Elem, typename Fun>
void for_each(Fun fun) {
    std::vector<Elem>& vector = std::get<std::vector<Elem>>(vectors);
    std::for_each(vector.begin(), vector.end(), std::move(fun));
}

template <typename Fun>
void ForEach(Fun fun) {
    int dummy[] = { 0, (for_each<Ts>(fun), 0)... };
    (void)dummy;
}

To pass a set of overloaded free functions, we can wrap them in a function object (thank @Yakk for the suggestion):

struct method_t
{
    template<class... Ts>
    void operator()(Ts&&... ts) const
    { method( std::forward<Ts>(ts)... ); }
};

In C++1y, such a function object type can be created with less boilerplate using a polymorphic lambda:

[](auto&&... pp)
{ method( std::forward<decltype(pp)>(pp)... ); }
Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • thank you this works, but I still have some questions. this works: vec.ForEach( [](auto t){ method(t); } ); but this doesn't: vec.ForEach( method ); I mean both can be called with () and both have a template parameter, why does the second fail? – Arne Aug 07 '14 at 21:57
  • @Arne `ForEach` needs to deduce a type for its function parameter. `method` is the name of a function template; such a thing does not have a (unique) type. A similar problem arises when you try to pass the name of a set of overloaded functions. There's no type *set of overloaded functions*, only a *reference/pointer to a specific function*. – dyp Aug 07 '14 at 22:02
  • @Arne A lambda-expression has class type, the polymorphic lambda presented in my answer essentially leads to the creation of a type like the `method_t` above; the lambda-expression is then an instance of that type. – dyp Aug 07 '14 at 22:05