Home > other >  Create class based on variadic templates
Create class based on variadic templates

Time:09-23

Lets imagine that we have several data flows and we need unite them to one. Capacity of flows (and type) is known at application level. So we need class that incapsulates all other classes that relates to each data flow and produces common frame based on return type of data flows. Each data flow class have next interface:

// note that there are several classes with similar interface
class FlowOne {
  using return_type = some_data;

  return_type get();
};

Main flow class will look like:

template <typename... Args>
class Main {
  using return_type = *based on Args::return_type*;
  return_type get();
};

So, here is a question: how to define Main::return_type - that should be a tuple of return_type-s from its Args? Is it possible?

Thanks

CodePudding user response:

You can expand Args... to be std::tuple<Args...> and declare a member of that type. Now your Main class has a tuple of all Flow types:

template <typename... Args>
struct Main {
    using tuple_type = std::tuple<Args...>;
    tuple_type members;

the next step is to make a get() function that returns the .get() of all members. This can be achieved with std::apply to iterate over the tuple, and call .get() on each object. Keep in mind that, if your final get() functions return a reference, this get() should also return one (meaning auto& or auto&&).

auto get() {
    return std::apply([](auto&&... member) {
        return std::make_tuple(member.get()...);
    }, this->members);
}

As you can see, if we say decltype(get) on the member function, it will be equal to the std::tuple of Flow types:

struct FlowOne {
    using return_type = int;

    return_type get() {
        return {};
    }
};
struct FlowTwo {
    using return_type = double;
    return_type get() {
        return {};
    }
};

int main() {
    Main<FlowOne, FlowTwo> main;
    auto get = main.get();

    static_assert(std::is_same_v<decltype(get), std::tuple<int, double>>);
}

if you still wish to define a Main::return_type, you can use this using statement:

using return_type = std::tuple<typename Args::return_type...>;

we have to add typename here, because return_type is a dependant type of Args. You can read more about this on this post


Try out the full code on godbolt.

CodePudding user response:

This calls all the flows and return a tuple with all the return values:

template <typename... Args>
class Main 
{
public:
    // Return tuple, with all the results of the Args::get()
    // e.g. if Args is <FlowOne, FlowTwo>, then tuple<int,float>
    using merged_type = std::tuple<decltype(Args::get())...>;
    
    merged_type get()
    {
        return {Args::get()...};
    }
};

Here is a full and working example:

#include <iostream>
#include <tuple>

struct FlowOne 
{
  using return_type = int;

  static return_type get()
  { 
      return 42; 
  }
};

struct FlowTwo 
{
  using return_type = float;

  static return_type get()
  {
      return 42.1;
  }
};

template <typename... Args>
class Main 
{
public:
    using merged_type = std::tuple<decltype(Args::get())...>;
    merged_type get()
    {
        return {Args::get()...};
    }
};

int main()
{
    const auto ret = Main<FlowOne, FlowTwo>().get();
    
    std::cout << std::get<0>(ret) << std::endl;
    std::cout << std::get<1>(ret) << std::endl;
}

You can test it here: https://onlinegdb.com/3nG0ffBUd

  • Related