6

Referring to the code below, can someone figure out how to adapt

template <typename RET, typename... ARGS1, typename... ARGS2>
RET Mediator::change (Object* o, RET (Object::*f)(ARGS1...), ARGS2&&... args) {
    const std::tuple<ARGS2...> t(args...);
    for (Object* x : objects)
        (x->*f)(std::get<0>(t), o->rating, std::get<1>(t), o->str);
}

so that I don't have to rewrite different versions of this every time ARGS2... is to be changed. I don't mind doing it in the case where the argument consists of only 4 parameters, but you can imagine that the generalization would be needed if it was much greater than 4. The types in ARGS1... shall consist of different types, so there should be a way get std::get<0>(t), std::get<1>(t), ... correctly placed so that there is no need to do it manually like above (even if there were duplicate types, then they can simply be placed in the first slot of the duplicate types). Here is the full code below (the context is that as each Object subscriber to a Mediator changes, the other Object subscribers to the Mediator shall change accordingly):

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

struct Mediator {
    std::vector<struct Object*> objects;

    void registerObject (Object* o) {objects.emplace_back(o);}

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (Object*, RET (Object::*)(ARGS1...), ARGS2&&...);
};

struct Object {
    int value;
    double rating;
    char letter;
    std::string str;
    Mediator& mediator;

    Object (int v, double r, char l, const std::string& s, Mediator& m) :
    value(v), rating(r), letter(l), str(s), mediator(m) {mediator.registerObject(this);}

    virtual void adjust (int, double, char, const std::string&) = 0;

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (RET (Object::*f)(ARGS1...), ARGS2&&... args) {
        return mediator.change(this, f, std::forward<ARGS2>(args)...);
    }
};

struct A : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, const std::string& s) override {
        std::cout << "Type A adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
};

struct B : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, const std::string& s) override {
        std::cout << "Type B adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
};

struct C : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, const std::string& s) override {
        std::cout << "Type C adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
};

template <typename RET, typename... ARGS1, typename... ARGS2>
RET Mediator::change (Object* o, RET (Object::*f)(ARGS1...), ARGS2&&... args) {
    const std::tuple<ARGS2...> t(args...);
    for (Object* x : objects)
        (x->*f)(std::get<0>(t), o->rating, std::get<1>(t), o->str);
}

int main() {
    Mediator mediator;
    Object *a = new A(6, 1.2, 'a', "alan", mediator);
    Object *b = new B(2, 6.5, 'b', "bob", mediator);
    Object *c = new C(4, 0.8, 'c', "craig", mediator);

    c->change (&Object::adjust, 8, 'k');
}

Output:

Type A adjusted using values 8, 0.8, k, and craig.
Type B adjusted using values 8, 0.8, k, and craig.
Type C adjusted using values 8, 0.8, k, and craig.

This is as far as I've gotten with my solution. It gives the same output, but the line marked // Here! is what I need generated automatically.

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

template <std::size_t...> struct index_sequence {};

template <std::size_t N, std::size_t... Is>
struct make_index_sequence_helper : make_index_sequence_helper<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct make_index_sequence_helper<0, Is...> {
    using type = index_sequence<Is...>;
};

template <std::size_t N>
using make_index_sequence = typename make_index_sequence_helper<N>::type;

struct Mediator {
    std::vector<struct Object*> objects;

    void registerObject (Object* o) {objects.emplace_back(o);}

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (Object*, RET (Object::*)(ARGS1...), ARGS2&&...);

    template <typename RET, typename... ARGS, std::size_t... Is>
    RET changeHelper (RET (Object::*)(ARGS...), const std::tuple<ARGS...>&, index_sequence<Is...>);
};

struct Object {
    int value;
    double rating;
    char letter;
    std::string str;
    Mediator& mediator;

    Object (int v, double r, char l, const std::string& s, Mediator& m) :
    value(v), rating(r), letter(l), str(s), mediator(m) {mediator.registerObject(this);}

    virtual void adjust (int, double, char, const std::string&) = 0;

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (RET (Object::*f)(ARGS1...), ARGS2&&... args) {
        return mediator.change(this, f, std::forward<ARGS2>(args)...);
    }
};

struct A : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, const std::string& s) override {
        std::cout << "Type A adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
};

struct B : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, const std::string& s) override {
        std::cout << "Type B adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
};

struct C : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, const std::string& s) override {
        std::cout << "Type C adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
};

template <typename RET, typename... ARGS1, typename... ARGS2>
RET Mediator::change (Object* o, RET (Object::*f)(ARGS1...), ARGS2&&... args) {
    const std::tuple<ARGS2...> t(args...);
      // Here!
    const std::tuple<ARGS1...> tuple(std::get<0>(t), o->rating, std::get<1>(t), o->str);
    changeHelper (f, tuple, make_index_sequence<sizeof...(ARGS1)>());
}

template <typename RET, typename... ARGS, std::size_t... Is>
RET Mediator::changeHelper (RET (Object::*f)(ARGS...),
        const std::tuple<ARGS...>& tuple, index_sequence<Is...>) {
    for (Object* x : objects)
        (x->*f) (std::get<Is>(tuple)...);   
}

int main() {
    Mediator mediator;
    Object *a = new A(6, 1.2, 'a', "alan", mediator);
    Object *b = new B(2, 6.5, 'b', "bob", mediator);
    Object *c = new C(4, 0.8, 'c', "craig", mediator);

    c->change (&Object::adjust, 8, 'k');
}

How to auto-generate the tuple

const std::tuple<ARGS1...> tuple(std::get<0>(t), o->rating, std::get<1>(t), o->str);

using something along the lines of

template <typename... ARGS1, typename... ARGS2>
std::tuple<ARGS1...> extractTuple (Object* o, ARGS2&&... args);

so that new versions of Mediator::change will not be needed for different (possibly many, if ARGS1... is large) choices of ARGS2... ? My current idea is to use a recursive helper method, std::is_same, std::tuple_cat, etc... but I'm having problems (I think we are unpacking ARGS2... within unpacking ARGS1... during the checking of types).

prestokeys
  • 4,817
  • 3
  • 20
  • 43
  • 5
    Note: by using all uppercase names for the template parameters you risk getting macro substitution. That's not so big a problem with single letter names, because few are crazy enough to define macros with single letter names. But e.g. `RET` stands a good chance of being the name of a macro. – Cheers and hth. - Alf Dec 15 '14 at 17:43
  • 1
    How do you know which argument goes where ? This interface seems bad for me because the position of the argument is the key to what you "adjust". You probably want to build a structure with named members (not a tuple) and call the function pointer with that. It's easier to follow & understand, and at least, it's no more dependent on argument count & position. – xryl669 Dec 15 '14 at 19:30
  • @xryl669. If all the types in ARGS1... are different types, there should be a way for the program to figure out what parameter goes where. I'm trying to work on a solution right now using a recursive helper, std::is_same, etc... But even if there are duplicate types in ARGS1..., my question stated that those from ARGS2... can simply be placed first in the duplicate slots, so the different-types case would still work. – prestokeys Dec 15 '14 at 22:47
  • 1
    I understand your issue, but again, I think like a user of this code, it's almost impossible to understand what argument should go where. Even if you had a lot of smartness in your "argument type deduction", still, by reading such code, it's not clear to me what to put in the function call (in what order, etc...). I think you'll spend less time designing the possible "struct ArgumentForFuncX", "struct ArgumentForFuncY" versions, and this is documented and understandable. – xryl669 Dec 16 '14 at 12:34
  • @xryl669, I've added more code in my question, including my latest attempt at the needed `std::tuple extractTuple (Object* o, ARGS2&&... args)`. It is getting closer to solving it, and my gut tells me that it should be possible (I'm thinking of defining struct concatenate> {using value = typename std::tuple; to help out). I'll put this question for bounty tomorrow if my attempt doesn't work. Once `extractTuple` is obtained, we're done. – prestokeys Dec 16 '14 at 15:32
  • I'm super confused as to what you're doing. Your `change` takes a polymorphic function, `RET (Object::*)(ARGS1...)`, but then only works if the parameters are `(int,double,char,string)`. Why the generic signature in that case? If the parameters are always fixed, this becomes much simpler: http://coliru.stacked-crooked.com/a/0d1c7f2074956cc9 – Mooing Duck Dec 16 '14 at 17:51
  • You found an alternate method to my specific example, but the parameters are not always fixed in ARGS1... I was merely running my tests with (int,double,char,string) in order to get a first solution going. My answer below states that it still needs to be generalized through some sort of recursion with ARGS1... so that it can also handle an Object member function that has arguments, say, `(char, double, int)`. Neither one of our solution will work with `c->change (&Object::foo, 'z', 4);` where `Object::foo` has signature `(char, double, int)`. – prestokeys Dec 16 '14 at 22:30
  • @Mooing Duck. I think I've generalized my solution to handle `c->change (&Object::foo, 'z', 4);`. I did unpack ARGS1... to allow any signature than just `(int,double,char,string)`, but I don't have C++14 to handle the apparent illegal declaration in C++11 of my `extractTuple`. Your solution I'm sure can be generalized too, and I will try to do so, because if possible it will be shorter than mine. – prestokeys Dec 17 '14 at 01:19

3 Answers3

2

First, we need a tag and a series of functions to get values from objects based on their types. Simple enough.

template<class T> struct typetag {};

const int& get_type_from_class(const Object* o, typetag<int>) {return o->value;}
const double& get_type_from_class(const Object* o, typetag<double>) {return o->rating;}
const char& get_type_from_class(const Object* o, typetag<char>) {return o->letter;}
const long& get_type_from_class(const Object* o, typetag<long>) {return o->tag;}

The next part is that we need to get types from a list of parameters based on their types, and the first parameter is the default to return if no parameters match. Also not insanely difficult. There's the recursive mismatch case, the recursive match case, and the last-is-match case. Though this appears to have a fair amount of recursion, even the simplest optimizer should be able to inline this to optimal assembly. For reasons I don't understand, these have to be in this exact order.

template<class T> 
const T& get_T_by_type(const T& def) 
{return def;}

template<class T, class...pRest> 
const T& get_T_by_type(const T& def, const T& returnme, const pRest&...rest) 
{return returnme;}

template<class T, class p0, class...pRest> 
const T& get_T_by_type(const T& def, const p0& discard, const pRest&...rest) 
{return get_T_by_type(def, rest...);}

Finally, we call the function. For each ARGS1, we call get_T_by_type to get the ARGS2 of the same type, and as the default we use get_type_from_class to pass the existing value in the class.

template <typename RET, typename... ARGS1, typename... ARGS2>
void Mediator::change (Object* o, RET (Object::*f)(ARGS1...), const ARGS2&... args) {
    for (Object* x : objects) {
        (x->*f)(
            get_T_by_type(get_type_from_class(o, typetag<ARGS1>{}),args...) //pass all args2
            ... //pass one of that for each args1
            );
    }
}

Note that I've changed the return type to void, since you're delegating to calling multiple functions. Alternatively, you could return a vector of the return results.

http://coliru.stacked-crooked.com/a/36afa072711b0655

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Very nice. I always enjoy reading shorter solutions. I tested that `c->change (&Object::transform, 'z', 4);` works too, where `Object::transform` has the signature (char, double, int). My generalized solution I think works too, but I don't have C++14 to fix my illegal declaration. But my solution is much too long anyway and I will study yours. Thanks for your help. I don't know why 2 people voted to close this thread. Perhaps I edited my posts too much. – prestokeys Dec 17 '14 at 01:54
  • I believe in your first solution, the line `(x->*f) (t.value, t.rating, t.letter, t.tag);` can be generalized by constructing a tuple very similar to my method. That is what I was going to try before you gave your new solution. I will still try that and thus get three solutions to this problem. – prestokeys Dec 17 '14 at 02:01
  • @prestokeys: I've never found much use for tuples, they always seemed overused to me. Simple function overloading is much simpler – Mooing Duck Dec 17 '14 at 17:15
  • Well, for what it's worth, I generalized your first solution using tuples and posted the answer, crediting it to you. It pretty much doubles your code and messy just to get the correct tuple. Your new answer is clearly better. – prestokeys Dec 17 '14 at 21:21
0

Ok, I have a first draft working. It still needs to be generalized further by unpacking ARGS1... instead of the way I did below. But at least, this first solution shows that the problem can probably be solved in its entirety.

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

template <std::size_t...> struct index_sequence {};

template <std::size_t N, std::size_t... Is>
struct make_index_sequence_helper : make_index_sequence_helper<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct make_index_sequence_helper<0, Is...> {
    using type = index_sequence<Is...>;
};

template <std::size_t N>
using make_index_sequence = typename make_index_sequence_helper<N>::type;

struct Mediator {
    std::vector<struct Object*> objects;

    void registerObject (Object* o) {objects.emplace_back(o);}

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (Object*, RET (Object::*)(ARGS1...), ARGS2&&...);

    template <typename RET, typename... ARGS, std::size_t... Is>
    RET changeHelper (RET (Object::*)(ARGS...), const std::tuple<ARGS...>&, index_sequence<Is...>);
};

struct Object {
    int value;
    double rating;
    char letter;
    long tag;
    Mediator& mediator;

    Object (int v, double r, char l, long s, Mediator& m) :
        value(v), rating(r), letter(l), tag(s), mediator(m) {mediator.registerObject(this);}

    virtual void adjust (int, double, char, long) = 0;
    virtual void transform (char, double, int) = 0;

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (RET (Object::*f)(ARGS1...), ARGS2&&... args) {
        return mediator.change(this, f, std::forward<ARGS2>(args)...);
    }
};

struct A : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type A adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type A transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

struct B : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type B adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type B transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

struct C : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type C adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type C transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

template <typename T, typename TUPLE> struct Concatenate;

template <typename FIRST, typename ...REST>
struct Concatenate<FIRST, std::tuple<REST...>> {
    using type = typename std::tuple<FIRST, REST...>;
};

template <typename HEAD, typename TUPLE>
typename Concatenate<HEAD, TUPLE>::type nextTuple (Object* o, const TUPLE& tuple) {
    if (std::is_same<HEAD, int>::value)
        return std::tuple_cat (tuple, std::tuple<int>(o->value));
    else if (std::is_same<HEAD, double>::value)
        return std::tuple_cat (tuple, std::tuple<double>(o->rating));
    else if (std::is_same<HEAD, char>::value)
        return std::tuple_cat (tuple, std::tuple<char>(o->letter));
    else if (std::is_same<HEAD, long>::value)
        return std::tuple_cat (tuple, std::tuple<long>(o->tag));
}

template <typename HEAD, typename TUPLE, typename FIRST, typename... REST>
typename Concatenate<HEAD, TUPLE>::type nextTuple (Object* o, const TUPLE& tuple, FIRST first, REST... rest) {
    if (std::is_same<HEAD, FIRST>::value)
        return std::tuple_cat (tuple, std::tuple<FIRST>(first));
    return nextTuple<HEAD, TUPLE, REST...> (o, tuple, rest...);
}

template <typename RET, typename... ARGS1, typename... ARGS2>
std::tuple<ARGS1...> extractTuple (Object* o, RET (Object::*)(ARGS1...), ARGS2&&... args) {
// Function pointer parameter needed to maintain ARGS1..., else it will become an empty pack.
    std::tuple<> t0;
    const auto t1 = nextTuple<int> (o, t0, args...);
        // In general, unpack ARGS1...
    const auto t2 = nextTuple<double> (o, t1, args...);
    const auto t3 = nextTuple<char> (o, t2, args...);
    return nextTuple<long> (o, t3, args...);
}

template <typename RET, typename... ARGS1, typename... ARGS2>
RET Mediator::change (Object* o, RET (Object::*f)(ARGS1...), ARGS2&&... args) {
    const std::tuple<ARGS1...> tuple = extractTuple (o, f, args...);  // The key function.
    changeHelper (f, tuple, make_index_sequence<sizeof...(ARGS1)>());
}

template <typename RET, typename... ARGS, std::size_t... Is>
RET Mediator::changeHelper (RET (Object::*f)(ARGS...), const std::tuple<ARGS...>& tuple, index_sequence<Is...>) {
    for (Object* x : objects)
        (x->*f) (std::get<Is>(tuple)...);   
}

int main() {
    Mediator mediator;
    Object *a = new A(6, 1.2, 'a', 1111, mediator);
    Object *b = new B(2, 6.5, 'b', 2222, mediator);
    Object *c = new C(4, 0.8, 'c', 3333, mediator);

    c->change (&Object::adjust, 8, 'k');
//  c->change (&Object::transform, 'z', 4);  // This does not work though.
}

Output:

Type A adjusted using values 8, 0, k, and 3333.
Type B adjusted using values 8, 0, k, and 3333.
Type C adjusted using values 8, 0, k, and 3333.

For some reason, there were issues with std::string, so I replaced the 4th parameter with long. So this solution still needs refinement. I've also introduced a new Object member function transform to show that the line c->change (&Object::transform, 'z', 4); in main() does NOT work (nor does it work in Mooing Duck's solution), and hence this solution needs more generalization, through some sort of recursion with ARGS1... probably, to be complete. My solution above only handles the specific ARGS1... in the form (int, double, char, long).

I suspect Mooing Duck's solution can also be generalized to handle any new member function of Object passed into Mediator::change.

UPDATE: Ok, I've replaced extractTuple with

template <typename TUPLE, typename... ARGS2>
TUPLE extractTuple (Object*, const TUPLE& tuple, ARGS2&&...) {return tuple;}

template <typename TUPLE, typename FIRST, typename... REST, typename... ARGS2>
auto extractTuple (Object* o, const TUPLE& current, ARGS2&&... args)
-> decltype (extractTuple<typename Concatenate<FIRST, TUPLE>::type, REST...>
(o, nextTuple<FIRST> (o, current, args...), args...)) {
    const typename Concatenate<FIRST, TUPLE>::type next = nextTuple<FIRST> (o, current, args...);
    return extractTuple<typename Concatenate<FIRST, TUPLE>::type, REST...> (o, next, args...);
}

and I think this generalizes the above solution by unpacking ARGS1... Here is my new code now:

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

template <std::size_t...> struct index_sequence {};

template <std::size_t N, std::size_t... Is>
struct make_index_sequence_helper : make_index_sequence_helper<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct make_index_sequence_helper<0, Is...> {
    using type = index_sequence<Is...>;
};

template <std::size_t N>
using make_index_sequence = typename make_index_sequence_helper<N>::type;

struct Mediator {
    std::vector<struct Object*> objects;

    void registerObject (Object* o) {objects.emplace_back(o);}

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (Object*, RET (Object::*)(ARGS1...), ARGS2&&...);

    template <typename RET, typename... ARGS, std::size_t... Is>
    RET changeHelper (RET (Object::*)(ARGS...), const std::tuple<ARGS...>&, index_sequence<Is...>);
};

struct Object {
    int value;
    double rating;
    char letter;
    long tag;
    Mediator& mediator;

    Object (int v, double r, char l, long s, Mediator& m) :
        value(v), rating(r), letter(l), tag(s), mediator(m) {mediator.registerObject(this);}

    virtual void adjust (int, double, char, long) = 0;
    virtual void transform (char, double, int) = 0;

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (RET (Object::*f)(ARGS1...), ARGS2&&... args) {
        return mediator.change(this, f, std::forward<ARGS2>(args)...);
    }
};

struct A : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type A adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type A transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

struct B : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type B adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type B transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

struct C : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type C adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type C transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

template <typename T, typename TUPLE> struct Concatenate;

template <typename FIRST, typename ...REST>
struct Concatenate<FIRST, std::tuple<REST...>> {
    using type = typename std::tuple<FIRST, REST...>;
};

template <typename HEAD, typename TUPLE>
typename Concatenate<HEAD, TUPLE>::type nextTuple (Object* o, const TUPLE& tuple) {
    if (std::is_same<HEAD, int>::value)  // Using overload from some new class can probably handle all these cases better.
        return std::tuple_cat (tuple, std::tuple<int>(o->value));
    else if (std::is_same<HEAD, double>::value)
        return std::tuple_cat (tuple, std::tuple<double>(o->rating));
    else if (std::is_same<HEAD, char>::value)
        return std::tuple_cat (tuple, std::tuple<char>(o->letter));
    else if (std::is_same<HEAD, long>::value)
        return std::tuple_cat (tuple, std::tuple<long>(o->tag));
}

template <typename HEAD, typename TUPLE, typename FIRST, typename... REST>
typename Concatenate<HEAD, TUPLE>::type nextTuple (Object* o, const TUPLE& tuple, FIRST first, REST... rest) {
    if (std::is_same<HEAD, FIRST>::value)
        return std::tuple_cat (tuple, std::tuple<FIRST>(first));
    return nextTuple<HEAD, TUPLE, REST...> (o, tuple, rest...);
}

template <typename TUPLE, typename... ARGS2>
TUPLE extractTuple (Object*, const TUPLE& tuple, ARGS2&&...) {return tuple;}

// *** The change
template <typename TUPLE, typename FIRST, typename... REST, typename... ARGS2>
auto extractTuple (Object* o, const TUPLE& current, ARGS2&&... args)
-> decltype (extractTuple<typename Concatenate<FIRST, TUPLE>::type, REST...>
(o, nextTuple<FIRST> (o, current, args...), args...)) {
    const typename Concatenate<FIRST, TUPLE>::type next = nextTuple<FIRST> (o, current, args...);
    return extractTuple<typename Concatenate<FIRST, TUPLE>::type, REST...> (o, next, args...);
}

template <typename RET, typename... ARGS1, typename... ARGS2>
RET Mediator::change (Object* o, RET (Object::*f)(ARGS1...), ARGS2&&... args) {
    const std::tuple<ARGS1...> tuple = extractTuple<std::tuple<>, ARGS1...> (o, std::tuple<>(), args...);  // The key function.
    changeHelper (f, tuple, make_index_sequence<sizeof...(ARGS1)>());
}

template <typename RET, typename... ARGS, std::size_t... Is>
RET Mediator::changeHelper (RET (Object::*f)(ARGS...), const std::tuple<ARGS...>& tuple, index_sequence<Is...>) {
    for (Object* x : objects)
        (x->*f) (std::get<Is>(tuple)...);   
}

int main() {
    Mediator mediator;
    Object *a = new A(6, 1.2, 'a', 1111, mediator);
    Object *b = new B(2, 6.5, 'b', 2222, mediator);
    Object *c = new C(4, 0.8, 'c', 3333, mediator);

    c->change (&Object::adjust, 8, 'k');
    c->change (&Object::transform, 'z', 4);
}

Note that c->change (&Object::transform, 'z', 4); is in main() now. But my GCC 4.8.1 appears bugged and cannot handle the declaration

template <typename TUPLE, typename FIRST, typename... REST, typename... ARGS2>
auto extractTuple (Object* o, const TUPLE& current, ARGS2&&... args)
-> decltype (extractTuple<typename Concatenate<FIRST, TUPLE>::type, REST...>
(o, nextTuple<FIRST> (o, current, args...), args...)) {

resulting in an "internal compiler error." Perhaps someone with a more recent compiler with C++14 can check if that is a legal declaration or not? I don't have C++14.

prestokeys
  • 4,817
  • 3
  • 20
  • 43
0

This answer is based on Mooing Duck's original answer, which only handled a special case. I added about double the amount of code to generalize his answer using tuples. His new general solution above is clearly superior though.

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

template <std::size_t...> struct index_sequence {};

template <std::size_t N, std::size_t... Is>
struct make_index_sequence_helper : make_index_sequence_helper<N-1, N-1, Is...> {};

template <std::size_t... Is>
struct make_index_sequence_helper<0, Is...> {
    using type = index_sequence<Is...>;
};

template <std::size_t N>
using make_index_sequence = typename make_index_sequence_helper<N>::type;

struct Mediator {
    std::vector<struct Object*> objects;

    void registerObject (Object* o) {objects.emplace_back(o);}

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (Object*, RET (Object::*)(ARGS1...), ARGS2&&...);

    template <typename RET, typename... ARGS, std::size_t... Is>
    RET changeHelper (RET (Object::*)(ARGS...), const std::tuple<ARGS...>&, index_sequence<Is...>);
};

struct Object {
    int value;
    double rating;
    char letter;
    long tag;
    Mediator& mediator;

    Object (int v, double r, char l, long s, Mediator& m) : value(v), rating(r), letter(l), tag(s), mediator(m) {mediator.registerObject(this);}

    virtual void adjust (int, double, char, long) = 0;
    virtual void transform (char, double, int) = 0;

    template <typename RET, typename... ARGS1, typename... ARGS2>
    RET change (RET (Object::*f)(ARGS1...), ARGS2&&... args) {
        return mediator.change(this, f, std::forward<ARGS2>(args)...);
    }
};

struct A : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type A adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type A transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

struct B : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type B adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type B transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

struct C : Object {
    using Object::Object;
    virtual void adjust (int a, double b, char c, long s) override {
        std::cout << "Type C adjusted using values " << a << ", " << b << ", " << c << ", and " << s << "." << std::endl;
    }
    virtual void transform (char a, double b, int c) override {
        std::cout << "Type C transformed using values " << a << ", " << b << ", and " << c << "." << std::endl;
    }
};

template <typename T, typename TUPLE> struct Concatenate;

template <typename FIRST, typename ...REST>
struct Concatenate<FIRST, std::tuple<REST...>> {
    using type = typename std::tuple<REST..., FIRST>;  // This time, we must concatenate at the back else 'extractTuple<std::tuple<>, ARGS1...>(t, std::tuple<>())' below will be in reverse!
};

struct NamedObjectChangeParameters {
    int value;
    double rating;
    char letter;
    long tag;

    explicit NamedObjectChangeParameters (const Object& o) : value(o.value), rating(o.rating), letter(o.letter), tag(o.tag) {}

    // change overloads for Mediator::change.
    void change (int a) {value = a;}
    void change (double b) {rating = b;}
    void change (char c) {letter = c;}
    void change (long d) {tag = d;}

    template <typename FIRST, typename... REST>
    void change (const FIRST& first, const REST&... rest) {
        change (first);
        change (rest...);
    }

    // nextTuple overloads for the important extractTuple function.
    template <typename TUPLE> typename Concatenate<int, TUPLE>::type
    nextTuple (int, const TUPLE& tuple) {return std::tuple_cat (tuple, std::tuple<int>(value));}
    template <typename TUPLE> typename Concatenate<double, TUPLE>::type
    nextTuple (double, const TUPLE& tuple) {return std::tuple_cat (tuple, std::tuple<int>(rating));}
    template <typename TUPLE> typename Concatenate<char, TUPLE>::type   
    nextTuple (char, const TUPLE& tuple) {return std::tuple_cat (tuple, std::tuple<int>(letter));}
    template <typename TUPLE> typename Concatenate<long, TUPLE>::type   
    nextTuple (long, const TUPLE& tuple) {return std::tuple_cat (tuple, std::tuple<int>(tag));}
};

template <typename TUPLE>
TUPLE extractTuple (NamedObjectChangeParameters&, const TUPLE& tuple) {return tuple;}

template <typename TUPLE, typename FIRST, typename... REST>
auto extractTuple (NamedObjectChangeParameters& t, const TUPLE& current)
-> decltype (extractTuple<typename Concatenate<FIRST, TUPLE>::type, REST...> (t, t.nextTuple<TUPLE> (FIRST(), current))) {
    const typename Concatenate<FIRST, TUPLE>::type next = t.nextTuple<TUPLE> (FIRST(), current);  // nextTuple<TUPLE> is an overloaded function of NamedObjectChangeParameters that creates the correct type based on what type FIRST is.
    return extractTuple<typename Concatenate<FIRST, TUPLE>::type, REST...> (t, next);
}

template <typename RET, typename... ARGS1, typename... ARGS2>
RET Mediator::change (Object* o, RET (Object::*f)(ARGS1...), ARGS2&&... args) {
    NamedObjectChangeParameters t(*o);
    t.change(args...);
    changeHelper (f, extractTuple<std::tuple<>, ARGS1...>(t, std::tuple<>()), make_index_sequence<sizeof...(ARGS1)>());
}

template <typename RET, typename... ARGS, std::size_t... Is>
RET Mediator::changeHelper (RET (Object::*f)(ARGS...), const std::tuple<ARGS...>& tuple, index_sequence<Is...>) {
    for (Object* x : objects)
        (x->*f) (std::get<Is>(tuple)...);
}

int main() {
    Mediator mediator;
    Object *a = new A(6, 1.2, 'a', 1111, mediator);
    Object *b = new B(2, 6.5, 'b', 2222, mediator);
    Object *c = new C(4, 0.8, 'c', 3333, mediator);

    c->change (&Object::adjust, 8, 'k');
    c->change (&Object::transform, 'z', 4);
}

Output:

Type A adjusted using values 8, 0, k, and 3333.
Type B adjusted using values 8, 0, k, and 3333.
Type C adjusted using values 8, 0, k, and 3333.
Type A transformed using values z, 0, and 4.
Type B transformed using values z, 0, and 4.
Type C transformed using values z, 0, and 4.
prestokeys
  • 4,817
  • 3
  • 20
  • 43