Steve Lorimer
Steve Lorimer

Reputation: 28699

Generically call member function on each element of a tuple

Step one: expand a tuple and pass elements to a function:

I have a function which takes N parameters

void func(int, double, char);

and a tuple with the matching types

std::tuple<int, double, char> tuple;

As per this stackoverflow question, I am able to expand the tuple and call the function.

Step two: expand a tuple and pass result of calling a member function on each of the elements to a function:

Taking it a step further, my tuple contains multiple instances of a class template:

template<typename T>
struct Foo;

std::tuple<Foo<int>, Foo<double>, Foo<char>>

Foo has a member function, Foo<T>::get() which returns a value of type T.

As per this stackoverflow answer, I have below working code which expands the tuple and calls element.get() on each element, ultimately passing the result to func.

The unfortunate thing is that I've hard-coded the call to element.get().

Step three: make the specification of which member function to call on each of the tuple elements generic:

(This is what I'm looking for help to do)

Is it possible to make this generic? That is, to pass which member function to call to apply, and therefore have it as a generic utility?

I thought perhaps I could use std::mem_fn to wrap a function (std::mem_fn(&Foo::get)) and pass the resulting object to apply, but that don't work because Foo is a class template:

error: ‘template<class T> struct Foo’ used without template parameters

Is there any way to make this generic?

Working example below:

#include <iostream>
#include <tuple>
#include <utility>

template<size_t...>
struct Seq
{ };

template<size_t N, size_t... Sq>
struct GenSeq : GenSeq<N - 1, N - 1, Sq...>
{ };

template<size_t... Sq>
struct GenSeq<0, Sq...>
{
    using type = Seq<Sq...>;
};

/////////////////////////////////////

struct Invoker
{
    template<typename Func, typename Tuple, size_t... Sq>
    static auto invoke(Func func, const Tuple& tuple, Seq<Sq...>)
        -> decltype(func(std::get<Sq>(tuple).get()...))
    {
        // calls the .get() member on each object in the tuple
        // I would like to make this generic
        return func(std::get<Sq>(tuple).get()...);
    }

    template<typename Func, typename... Args>
    static auto apply(Func func, const std::tuple<Args...>& args)
        -> decltype(invoke(func, args, typename GenSeq<sizeof...(Args)>::type()))
    {
        return invoke(func, args, typename GenSeq<sizeof...(Args)>::type());
    }
};

template<typename Func, typename Tuple>
inline auto apply(Func func, const Tuple& tuple)
    -> decltype(Invoker::apply(func, tuple))
{
    return Invoker::apply(func, tuple);
}

///////////////////////////////////////

template<typename T>
struct Foo
{
    T i;
    auto get() const -> decltype(i) { return i; }
};

template<typename... Ts>
struct Bar
{
    Bar(Ts... ts) : tuple(std::make_tuple(Foo<Ts> { ts }...)) {}
    std::tuple<Foo<Ts>...> tuple;
};

void func(int i, double d, char c)
{
    std::cout << i << ", " << d << ", " << c << std::endl;
}

int main()
{
    Bar<int, double, char> bar { 4, 1.23, 'a' };
    apply(func, bar.tuple);
}

Upvotes: 3

Views: 1595

Answers (1)

T.C.
T.C.

Reputation: 137394

You can't use mem_fn to create a wrapper that calls a member function on objects of heterogeneous type, as the wrapper created by mem_fn wraps a pointer to a particular member of a particular type.

The trick is to pass something with a templated function call operator that can accept any appropriate type and call the member function.

In C++14, pass a polymorphic lambda:

[](auto&& obj) -> decltype(auto) { return std::forward<decltype(obj)>(obj).get(); }

For C++11, you'd need to write the equivalent functor by hand:

struct call_get {
    template<class T>    
    auto operator()(T&& obj) const -> decltype(std::forward<T>(obj).get()) {
         return std::forward<T>(obj).get(); 
    } 
};

Upvotes: 4

Related Questions