10

I am trying to create a generic class that takes a pack of types, stores them in a tuple, and can apply a function over them.

What I tried so far is the following:

#include <tuple>
struct Base{
    virtual void base_function() = 0;
};

template<typename ...T>
struct A : public Base{
    std::tuple<T...> as;
    A(T... pack):as(pack...){};
    void base_function(){
        std::apply([](auto t){t.base_function();}, as);
    }
};

struct B : public Base{
    void base_function(){};
};


struct C : public Base{
    void base_function(){};
};

struct D : A<B, C>{
    D():A(B(),C()){};
};

I expected apply to be called on base_function from class B and C when calling base_function on D. But the compiler generates the following error:

error: no matching function for call to
'__invoke(A<T>::base_function() [with T = {B, C}]::<lambda(auto:1)>, std::__tuple_element_t<0, std::tuple<B, C> >&, std::__tuple_element_t<1, std::tuple<B, C> >&)'

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
sqrtroot
  • 167
  • 2
  • 9
  • Just so you know, the `Base` and `D` classes (and any inheritance from `Base`) aren't necessary for a minimal example. Your problem manifests itself the same way (and @Jarod42's solution works the same way) even without them. – Spencer Feb 11 '19 at 15:13
  • @Spencer Removing the D class doesn't generate the error because the compiler doesn't generate any code without a version of A being declared. The base class was there when slimming down the code and is there to give some context – sqrtroot Feb 12 '19 at 17:51
  • But you could have just declared an `A` wherever you declared the `D` object. – Spencer Feb 12 '19 at 22:56

2 Answers2

16

First parameter of std::apply should be a functor with same arity that number of elements of the tuple, so variadic in your case:

template <typename ...Ts>
struct A : public Base{
    std::tuple<Ts...> as;
    A(Ts... pack) : as(pack...){}

    void base_function(){
        std::apply([](auto&... ts){(ts.base_function(), ...);}, as);
    }
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Never seen the syntax used in the lambda, could you explain a bit more what it's actually doing. How does the compiler know what to do on the parameter pack `ts`? – sqrtroot Feb 11 '19 at 13:38
  • 5
    Look at [fold expression](https://en.cppreference.com/w/cpp/language/fold). – Jarod42 Feb 11 '19 at 13:40
  • This is great, but you might want to add an explanation as to why `std::apply` doesn't work the way OP thought it did. – Spencer Feb 11 '19 at 15:06
4

std::apply is not doing what you think. It is for a passing a tuple of parameters to a function (Callable type). In other words, the tuple itself doesn't have a function called base_function. see https://en.cppreference.com/w/cpp/utility/apply

darune
  • 10,480
  • 2
  • 24
  • 62
  • 2
    I'd interpret OP's confusing as thinking `apply` takes a unary function and applies it to each element in the tuple in turn, not as applying a unary function directly to the tuple (which would be pointless, as you could just... do it already). – Barry Feb 11 '19 at 15:41