0

I need to initialize a base class with arguments stored in a std::tuple. I have access to C++17 and managed to figure out that std::make_from_tuple may work but would require a copy constructor for the base class.

An example:

#include <tuple>

template<class Base>
class WithTupleConstructor : public Base // Sort of a Mixin class template
{
    public:

    // Creating a Base with arguments stored in tuple and then copying it
    template<class Tuple>
    WithTupleConstructor(const Tuple& base_args)
    : Base(std::make_from_tuple<Base>(base_args)) }
    { }
};

class WithCopyConstructor
{
    public:
    
    WithCopyConstructor(int a, int b)
    {};
    WithCopyConstructor(const WithCopyConstructor& other)
    {};
};

class WithoutCopyConstructor
{
    public:
    WithoutCopyConstructor(int a)
    {};
    WithoutCopyConstructor(const WithoutCopyConstructor& other) = delete;
};

int main()
{
  WithTupleConstructor<WithCopyConstructor> m1(std::make_tuple(1,2));

  // this do not compiles
  //WithTupleConstructor<WithoutCopyConstructor> m2(std::make_tuple(1));
}

std::make_index_sequence and std::get seem to demand an auxiliary function and cannot see how they could be used to solve this (as explained here tuple-to-parameter-pack).

Is there a way to expand the tuple in the initialize list without requiring the copy constructor?

CaTo
  • 69
  • 1
  • 8

2 Answers2

2

I found a solution based in this answer, turns out that it is achievable using std::make_index_sequence and std::get.

An auxiliary function that "unwraps" the tuple is required but it can be defined as a private constructor:

#include <tuple>

template<typename Tuple>
using make_tuple_index_sequence = std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>;

template<class Base>
class WithTupleConstructor : public Base // Sort of a Mixin class template
{
    public:

    // Passing tuple and index sequence to auxiliary constructor
    template<class Tuple>
    WithTupleConstructor(const Tuple& base_args)
    : WithTupleConstructor(base_args, make_tuple_index_sequence<Tuple>{})
    {}

    private:

    // Expanding tuple using std::get
    template<class Tuple, std::size_t ...tuple_n>
    WithTupleConstructor(const Tuple& base_args, std::index_sequence<tuple_n...> )
    : Base(std::get<tuple_n>(base_args)...)
    {}
};

class WithCopyConstructor
{
    public:
    
    WithCopyConstructor(int a, int b)
    {}
    WithCopyConstructor(const WithCopyConstructor& other)
    {}
};

class WithoutCopyConstructor
{
    public:

    WithoutCopyConstructor(int a)
    {}
    WithoutCopyConstructor(const WithoutCopyConstructor& other) = delete;
};

int main()
{
    WithTupleConstructor<WithCopyConstructor> m1(std::make_tuple(1,2));

    WithTupleConstructor<WithoutCopyConstructor> m2(std::make_tuple(1));
}

This can be further extended to support rvalues and perfect forwarding by adding && and std::forward when receiving and passing the tuples.

YurkoFlisk
  • 873
  • 9
  • 21
CaTo
  • 69
  • 1
  • 8
0

std::make_from_tuple will return a type Base, which will then be used by Base's copy constructor to create m1 or m2, as you mentioned.

I don't know if a different approach as the one I show below would fit you:

  • WithTupleConstructor just forwards the tuple to Base.
  • Each Base has a constructor receiving a specific tuple.

[Demo]

#include <fmt/core.h>
#include <tuple>
#include <utility>  // forward

template<typename Base>
struct WithTupleConstructor : public Base {
    template<typename Tuple>
    WithTupleConstructor(Tuple&& base_args)
    : Base{ std::forward<Tuple>(base_args) }
    {}
};

struct WithCopyConstructor {
    int a_{};
    int b_{};

    WithCopyConstructor(std::tuple<int,int>&& t)
    : a_{std::move(std::get<0>(t))}, b_{std::move(std::get<1>(t))} {}
    WithCopyConstructor(int a, int b) {};
    WithCopyConstructor(const WithCopyConstructor& other) {};
};

struct WithoutCopyConstructor {
    int a_{};

    WithoutCopyConstructor(std::tuple<int>&& t)
    : a_{std::move(std::get<0>(t))} {}
    WithoutCopyConstructor(int a) {};
    WithoutCopyConstructor(const WithoutCopyConstructor& other) = delete;
};

int main() {
    WithTupleConstructor<WithCopyConstructor> m1(std::tuple{1,2});
    WithTupleConstructor<WithoutCopyConstructor> m2(std::tuple{1});

    fmt::print("m1: ({}, {})\n", m1.a_, m1.b_);
    fmt::print("m2: ({})\n", m2.a_);
}

// Outputs:
//
//   m1: (1, 2)
//   m2: (1)
rturrado
  • 7,699
  • 6
  • 42
  • 62
  • 1
    The problem is in the context of a heavy generic programming framework and I don't really have access to the base classes. I'm trying not to add requirements on their constructors/interface. – CaTo Aug 20 '22 at 10:37