Is there a way to distribute multiple parameter packs from template class constructor to some functors?

57 Views Asked by At

Here is some code that not work:

#include<iostream>

template<typename T1, typename T2, typename T3>
class Composer {
    // pseudocode of constructor
    template<typename... Args_one, typename... Args_two, typename... Args_three>
    explicit Composer(Args_one...args1, Args_two...args2, Args_three...args3) {
        t1_ = T1(args1...);
        t2_ = T2(args2...);
        t3_ = T3(args3...);
    }

    double operator()(int u) {
        auto x1 = t1_(u);
        auto x2 = t2_(float(x1));
        auto x3 = t3_(double(x2));
        return x3;
    }
private:
    T1 t1_;
    T2 t2_;
    T3 t3_;
};

class StageOne {
public:
    StageOne(int a, int b): a_(a), b_(b) {};
    int operator()(int c) const {
        return a_ + b_ + c;
    }
private:
    int a_;
    int b_;
};

class StageTwo {
public:
    StageTwo(float c, float d): c_(c), d_(d) {};
    float operator()(float i) const {
        return c_ + d_ + i;
    }
private:
    float c_;
    float d_;
};

class StageThree {
public:
    StageThree(double x, double y): x_(x), y_(y) {};
    double operator()(double z) const {
        return (x_  + y_) * z;
    }
private:
    double x_;
    double y_;
};

int main()
{
    Composer<StageOne, StageTwo, StageThree> composer{{3, 4}, {1.2f, 2.3f}, {4.5, 3.3}};
    std::cout << composer(3) << std::endl;
}

The Composer template is used to chain three functors. Each functors need to be initializd.

I know I can add methods like setStageOne that utilize a single parameter pack to do the initialization, but I will have to add three such methods and cannot forget to call it afterwards.

So I want the Composer's constructor take the work to set up the functors. Is there a way to do it?

2

There are 2 best solutions below

0
Ted Lyngmo On BEST ANSWER

You could put the arguments in std::tuples. Since the types are not default-constructible, you'll need a helper to initialize them from the supplied argument via std::apply (called init below).

Example:

#include <tuple>
#include <utility>

template <class T1, class T2, class T3>
class Composer {
    template <class T, class... Args>
    T init(std::tuple<Args...>& t) {
        return std::apply(
            [](auto&&... args) {
                return T{std::forward<decltype(args)>(args)...};
            }, t);
    }
public:

    template <class... Args_one, class... Args_two, class... Args_three>
    explicit Composer(std::tuple<Args_one...> args1,
                      std::tuple<Args_two...> args2,
                      std::tuple<Args_three...> args3)
        : t1_(init<T1>(args1)), t2_(init<T2>(args2)), t3_(init<T3>(args3)) {}

    double operator()(int u) {
        auto x1 = t1_(u);
        auto x2 = t2_(float(x1));
        auto x3 = t3_(double(x2));
        return x3;
    }

private:
    T1 t1_;
    T2 t2_;
    T3 t3_;
};

Demo

0
NathanOliver On

You can do like the standard does with the emplace functions and provide an overload that takes a std::piecewise_construct and tuples of the parameters and forward those into the construction of the members. That could look like

template <class T, class Tuple>
T construct_from_tuple(Tuple&& tuple) {
    return [&]<std::size_t... Is>(std::index_sequence<Is...>) 
           { return T{std::get<Is>(std::forward<Tuple>(tuple))...}; }
           (std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>{});
}

template<typename T1, typename T2, typename T3>
class Composer {
public:
    // pseudocode of constructor
    template<typename Tuple1, typename Tuple2, typename Tuple3>
    explicit Composer(std::piecewise_construct_t, Tuple1&& t1, Tuple2&& t2, Tuple3&& t3) 
     : t1_(construct_from_tuple<T1>(t1)), t2_(construct_from_tuple<T2>(t2)), t3_(construct_from_tuple<T3>(t3)) {}
        
    double operator()(int u) {
        auto x1 = t1_(u);
        auto x2 = t2_(float(x1));
        auto x3 = t3_(double(x2));
        return x3;
    }
private:
    T1 t1_;
    T2 t2_;
    T3 t3_;
};

class StageOne {
public:
    StageOne(int a, int b): a_(a), b_(b) {};
    int operator()(int c) const {
        return a_ + b_ + c;
    }
private:
    int a_;
    int b_;
};

class StageTwo {
public:
    StageTwo(float c, float d): c_(c), d_(d) {};
    float operator()(float i) const {
        return c_ + d_ + i;
    }
private:
    float c_;
    float d_;
};

class StageThree {
public:
    StageThree(double x, double y): x_(x), y_(y) {};
    double operator()(double z) const {
        return (x_  + y_) * z;
    }
private:
    double x_;
    double y_;
};

int main()
{
    Composer<StageOne, StageTwo, StageThree> composer{std::piecewise_construct, std::forward_as_tuple(3, 4), std::forward_as_tuple(1.2f, 2.3f), std::forward_as_tuple(4.5, 3.3)};
    std::cout << composer(3) << std::endl;
}

and cann be seen working in this live example.