Using std::apply with variadic packs
Asked Answered
K

2

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> >&)'

Kawasaki answered 11/2, 2019 at 13:21 Comment(3)
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.Partition
@Partition 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 contextKawasaki
But you could have just declared an A<B,C> wherever you declared the D object.Partition
T
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);
    }
};
Terpineol answered 11/2, 2019 at 13:32 Comment(3)
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?Kawasaki
Look at fold expression.Terpineol
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.Partition
A
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

Allanadale answered 11/2, 2019 at 13:32 Comment(1)
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).Jesicajeske

© 2022 - 2024 — McMap. All rights reserved.