0

I would like to have a container that can ingest an object, and store different parts of it into different internal containers.

Something like this:

// Pseudo-code

template<typename A, typename B, typename C>
class Container {
   std::vector<B> vb;
   std::vector<C> vc;
public:
   void push_back(const A &a) {
      vb.push_back(a);
      vc.push_back(a);
   }
};

class AA {
   int a;
   std::string s;
};

class BB {
   int a;
};

class CC {
   std::string s;
};

Container<AA, BB, CC> cont;

AA aa;
cont.push_back(aa);

I would like object vb to get what is in common between classes A an B (slicing), and the same for object vc.

Ideally, classes AA, BB and CC should not be in a hierarchical relation. I would like the compiler to be able to match the members by their type.

I would prefer a solution with small performance penalties.

Pietro
  • 12,086
  • 26
  • 100
  • 193
  • 1
    Object slicing is a feature of class hierarchy. Trying to implement something similar without Inheritance would be quite tricky without reflection. – eerorika Jul 19 '18 at 11:27
  • 1
    _"the compiler to be able to match the members by their type"_, which is pretty much asking the compiler to read your mind? You have to specify what you want one way or another. – Passer By Jul 19 '18 at 12:04
  • @PasserBy: It depends. If there are no member variables with the same type, there is no need to read my mind. If there are, it would be nice if the compiler could match members with the same type and same name. – Pietro Jul 19 '18 at 13:41

2 Answers2

4

Your pseudocode container already works in the case where AA inherits BB and CC, because slicing allows implicit conversion from derived to base. However, your implementation isn't limited to such cases. All that is required is for AA to be implicitly convertible to both BB and CC. Inheritance is sufficient for that, but not a requirement.

You can make a class convertible to others by defining conversion operators:

struct AA {
   operator BB() const { return {a}; }
   operator CC() const { return {s}; }
   int a;
   std::string s;
};

Alternatively, you can add a converting constructor to BB and/or CC if you would prefer the dependency to go the other way:

struct BB {
   BB(const AA& aa) : a(aa.a) {}
   int a;
};

Do note however, that narrowing implicit conversions such as this may cause certain bugs to go unnoticed.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Do you mean: `BB operator() const { return {a}; }` ? But if this is the case, the compiler cannot pick the right operator on the basis of the return type. – Pietro Jul 19 '18 at 13:44
  • @Pietro no, I don't mean that since that is ill-formed. The conversion operator is picked based on the *argument* type of `push_back`. – eerorika Jul 19 '18 at 13:53
  • Sorry, I did not get it. Is `operator BB() const` pseudo-code, or is it proper C++? Shouldn't the operator have a return type? Is BB() a valid operator? – Pietro Jul 19 '18 at 14:03
  • 1
    @Pietro this is all correct C++. This is the syntax of a conversion operator. `BB` is the return type. – eerorika Jul 19 '18 at 14:06
2

You can use tag dispatching to classify types and conditionally pick their members. An example:

template<class T> typename std::is_same<decltype(T::s), std::string>::type test_has_string_s(int);
template<class T> std::false_type test_has_string_s(...);
template<class T> using HasStringS = decltype(test_has_string_s<T>(0));

template<class T> typename std::is_same<decltype(T::a), int>::type test_has_int_a(int);
template<class T> std::false_type test_has_int_a(...);
template<class T> using HasIntA = decltype(test_has_int_a<T>(0));

template<typename B, typename C>
class Container {
    std::vector<B> vb;
    std::vector<C> vc;

    template<class T> void push_back_vb(T const& t, std::true_type) { vb.push_back(B{t.s}); }
    template<class T> void push_back_vb(T const&, std::false_type) {}

    template<class T> void push_back_vc(T const& t, std::true_type) { vc.push_back(C{t.a}); }
    template<class T> void push_back_vc(T const&, std::false_type) {}

public:
    template<class T>
    void push_back(T const& t) {
        static_assert(HasStringS<T>::value || HasIntA<T>::value, "Must have any of {T::a, T::s}.");
        push_back_vb(t, HasStringS<T>{});
        push_back_vc(t, HasIntA<T>{});
    }
};

struct AA {
   int a;
   std::string s;
};

struct BB {
   std::string s;
};

struct CC {
   int a;
};

struct DD {};

int main() {
    Container<BB, CC> cont;
    cont.push_back(AA{});
    cont.push_back(BB{});
    cont.push_back(CC{});
    cont.push_back(DD{}); // error: static assertion failed: Must have any of {T::a, T::s}.
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271