0

If base class is unknown to library (it is known to client), it is not so difficult to handle its constructor. The code looks like:

template<typename Parent>
struct AAAAA : public Parent
{
    using Parent::Parent;

    template<typename ...Args>
    AAAAA(int a, int b, Args ...args) : Parent(args...) {}
};

What is the best approach, if all>1 base classes are unknown?

template<typename P1, typename P2>
struct AAAAA : public P1, public P2
{
    // ...CTOR....???
};

My first thoughts are these:

  • A parameter pack "split" type.
  • 2 tuples which converted to parameter packs.

For both thoughts, I don't know this time how, and if it is possible.

Chameleon
  • 1,804
  • 2
  • 15
  • 21
  • 3
    I'd try not to end up in that design. Why is `AAAAA` inheriting from those classes in the first place? If you give a real example of what you want to accomplish, perhaps someone comes up with a nicer way to do it. – Ted Lyngmo Feb 12 '20 at 21:55
  • 1
    The two tuples is a perfectly cromulent approach. – Sam Varshavchik Feb 12 '20 at 22:05
  • @Ted Lyngmo We have a stream of serialized calculations. Every class in hierarchy is a calculation stage. So, T1 is previous calculation stage. T2 is a calculation converter. Both T1 and T2 satisfy their corresponding concepts BUT their constructors have UNKNOWN number and type of parameters. – Chameleon Feb 12 '20 at 22:14
  • All these unknown classes must have some interface in common making it possible to connect them. I don't see why there has to be inheritance though. Is `AAAAA` really both a `T1` and a `T2`? – Ted Lyngmo Feb 12 '20 at 22:21
  • @TedLyngmo Most member functions of T1 and T2, must exist in AAAAA. So it is common code. For some obscure reason, community says that common code must be encapsulated and not inherited. No. My code works. No. This is not the question. Common interface exists via concept. But not in constructor and setters/getters. Even with encapsulation of 2 classes, the same question about initialization arises. – Chameleon Feb 13 '20 at 10:07
  • 1
    @Chameleon It's usually cleaner to use composition unless `AAAAA` is actually a `T1` or `T2` (or in rare cases a mix of both, like a `stream` that is both an `istream` and `ostream`). Anyway, my answer below would work for both composition, and as it is in the answer, for inheritance and is i.m.o more userfriendly than requiring the user to pack the arguments into tuples. The effect with be much the same. – Ted Lyngmo Feb 13 '20 at 10:17

2 Answers2

1

What comes in handy here is std::make_from_tuple.

This is how you can use a tuple for a single parent:

#include <tuple>
struct foo { 
    foo(int,double){}
    foo(const foo&) = delete;
    foo(foo&&) = default;     
};

template<typename Parent>
struct A : public Parent
{
    template<typename T>
    A(const T& args) : Parent(std::make_from_tuple<Parent>(args)) {}
};

int main() {
    A<foo> a{std::make_tuple(1,2.0)};
}

Adding a second parent should be straightforward.

Note that Parent has to be at least move-constructible to make this work.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
1

You could require the clients to provide already constructed objects. It's easy to understand and doesn't require much typing. This requires them to be move-constructible.

#include <iostream>
#include <utility>

struct foo { 
    foo(int x, double y) { std::cout << x << ' ' << y << '\n'; }
};

struct bar { 
    bar(const std::string& x) { std::cout << x << '\n'; }
};

template<typename P1, typename P2>
struct A : public P1, public P2 {
    A(P1&& p1, P2&& p2) : P1(std::move(p1)), P2(std::move(p2)) {}
};

int main() {
    A<foo, bar> afb({1, 2.3}, {"hello"});
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108