Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to avoid the need for copy/move constructors in the following code?

Please consider the following code for a type that can compose different mixin types. The constructor of the composition type takes a variadic list of tuples that represent the arguments for the constructors of the composed types:

#include <string>
#include <tuple>
#include <utility>

struct MixinBase {
  MixinBase() = default;
  // Note; want to delete these instead of default them.
  MixinBase(const MixinBase&) = default;
  MixinBase(MixinBase&&) = default;
};

struct MixinA : public MixinBase {
  MixinA(int, const std::string&, const std::string&) {}
};

struct MixinB : public MixinBase {
  MixinB(const std::string&, const std::string&) {}
};

template <typename... Mixins>
struct Composition : private Mixins... {
  template <typename... Packs>
  Composition(Packs&&... packs)
      : Mixins(constructMixin<Mixins>(
            std::forward<Packs>(packs),
            std::make_index_sequence<std::tuple_size_v<Packs>>{}))...
  {
  }

private:
  template <typename Mixin, typename Pack, size_t... Indexes>
  Mixin constructMixin(Pack&& arguments, std::index_sequence<Indexes...>) const
  {
    return Mixin(std::get<Indexes>(std::forward<Pack>(arguments))...);
  }
};

int main()
{
  std::string a{"a"};
  std::string b{"b"};

  Composition<MixinA, MixinB>(
      std::forward_as_tuple(7, a, b), std::forward_as_tuple(a, b));

  return 0;
}

This works perfectly fine, however, I would like to avoid the indirection through constructMixin, and directly construct every inherited mixin object so that the need for a copy/move constructor on the mixin type can be avoided. Is this possible?

like image 711
Ton van den Heuvel Avatar asked Mar 06 '23 13:03

Ton van den Heuvel


2 Answers

You can define a helper class to support piecewise construction:

template <typename T>
struct Piecewise_construct_wrapper : T {
    template <typename Tuple>
    Piecewise_construct_wrapper(Tuple&& t) : 
        Piecewise_construct_wrapper(std::forward<Tuple>(t), 
                                    std::make_index_sequence<std::tuple_size_v<Tuple>>{}) {}

    template <typename Tuple, size_t... Indexes>
    Piecewise_construct_wrapper(Tuple&& t, std::index_sequence<Indexes...>) : 
        T(std::get<Indexes>(std::forward<Tuple>(t))...) {}
};

Then you can make your Composition inherit Piecewise_construct_wrapper<Mixins>...:

template <typename... Mixins>
struct Composition : private Piecewise_construct_wrapper<Mixins>... {
    template <typename... Packs>
    Composition(Packs&&... packs)
        : Piecewise_construct_wrapper<Mixins>(std::forward<Packs>(packs))...
    {
    }
};
like image 181
xskxzr Avatar answered Mar 08 '23 09:03

xskxzr


I would like to avoid the indirection through constructMixin, and directly construct every inherited mixin object so that the need for a copy/move constructor on the mixin type can be avoided. Is this possible?

You would need the Mixins to opt-in to allowing piecewise construction directly. Unfortunately that's pretty repetitive, so you could use a macro like:

#define PIECEWISE_CONSTRUCT(Type)                               \
  template <typename Tuple>                                     \
  Type(std::piecewise_construct_t, Tuple&& tuple)               \
    : Type(std::piecewise_construct,                            \
        std::forward<Tuple>(tuple),                             \
        std::make_index_sequence<std::tuple_size_v<Tuple>>())   \
  { }                                                           \  
  template <typename Tuple, size_t... Indexes>                  \
  Type(std::piecewise_construct_t, Tuple&& tuple,               \
        std::index_sequence<Indexes...>)                        \
    : Type(std::get<Indexes>(std::forward<Tuple>(tuple))...)    \
  { }

To be used as:

struct MixinA : public MixinBase {
  MixinA(int, const std::string&, const std::string&) {}

  PIECEWISE_CONSTRUCT(MixinA)
};

struct MixinB : public MixinBase {
  MixinB(const std::string&, const std::string&) {}

  PIECEWISE_CONSTRUCT(MixinB)
};

template <typename... Mixins>
struct Composition : private Mixins... {
  template <typename... Packs>
  Composition(Packs&&... packs)
      : Mixins(std::piecewise_construct, std::forward<Packs>(packs))...
  { }
};

Guaranteed copy elision unfortunately can't work in constructing subobjects - so this might be your best bet. I don't think you can do this any more directly without having some kind of nested packs? It's very possible that I'm just not creative enough though and would be very curious if somebody comes up with something better.

like image 33
Barry Avatar answered Mar 08 '23 08:03

Barry