1

I am trying to build the following architecture:

struct A{
   int x;
}
struct B: public A{
   int additinal_data;
}
struct ContainerA{
  std::vector<A> va; 
}
struct ContianerB{
  std::vector<B> vb;
  int additional_data;
}

I want to reorganize the following code in a way I would be able to call a function not only for ContainerA type but also for ContainerB.

double sum(ContainerA &ca) {
   double res = 0;
   for (const auto &a: ca.va) 
       res += a.x;
   return res;
}

It would also be nice to have an ability to move a simular function as a member of ContainerA but be accessible from ContainerB as well (i.e ContainerA ca; ca.sum()) However, ContinerB is not covariant to ContainerA and I struggle to make it so. How can I rework my architecture to allow such a call?

Solon
  • 362
  • 1
  • 13
  • 1
    Why not add an overload for `ContianerB`? – NathanOliver Aug 18 '20 at 16:05
  • 1
    How about `struct ContianerB : public ContainerA`? – goodvibration Aug 18 '20 at 16:07
  • As I don't know other requirements on your classes, my shot would be to make a parent class for both `ContainerA` and `ContainerB`, and use it as a polymorphic container. – Miguel Aug 18 '20 at 16:08
  • @goodvibration I don't know how to do it without data duplication. As I need to store vector in ContrainerA and vector in ContainerB where B extends A – Solon Aug 18 '20 at 16:11
  • See the answer of _Steve Jessop_ for [this question](https://stackoverflow.com/questions/1260757/when-is-c-covariance-the-best-solution), which uses a virtual `getIdentifier` –  Aug 18 '20 at 16:15
  • @BlayerBond: you probably wanna say [this answer](https://stackoverflow.com/a/1261355/7400903), since the rep will change (and your comment will not). – goodvibration Aug 18 '20 at 16:17
  • @goodvibration (Off-topic): Yes, I already replaced it with the author. How do I get the link to an answer? –  Aug 18 '20 at 16:19
  • @BlayerBond below the answer *share* – 463035818_is_not_an_ai Aug 18 '20 at 16:19
  • @BlayerBond: Using the `share` text at the bottom of the answer. – goodvibration Aug 18 '20 at 16:20
  • @BlayerBond Thank you, I've read it through however didn't manage to relate it to my question. My question is not about covariant return types, but about how to express that `ContainerB` "extends" `ContainerA` – Solon Aug 18 '20 at 16:22
  • I would also prefer not to deal with runtime polymorphism here as it looks like an overkill – Solon Aug 18 '20 at 16:25
  • @Solon, I was trying to code a working example with non-`const&` return types, but I failed at it, so I decided not to add an answer. (I'm used to have a `virtual CoParent* Identifier()`.) Why is it related? In case `ContainterB` behaves as `ContainterA` or `struct ContianerB : public ContainerA`, then you could provide `ContainerA` a method `virtual A* getElement(const int& index);` and provide `ContainerB` a method `B* getElement(const int& index) override;`. This way, you don't need a `std::vector` member in `ContainerB`, because you _know_ elements of `va` will be of type `B`. –  Aug 18 '20 at 19:15

4 Answers4

2

One way to use sum with either a ContainerA or ContainerB.

  1. Make the function a function template.
  2. Update ContainerA and ContainerB so they can be used in a range-for loop and many of the functions in the <algorithm> header of the standard library.
struct ContainerA
{
  std::vector<A> va; 

  // Add const and non-const versions of begin() and end().

  using iterator = std::vector<A>::iterator;
  using const_iterator = std::vector<A>::const_iterator;

  iterator begin() { return va.begin(); }
  iterator end() { return va.end(); }

  const_iterator begin() const { return va.begin(); }
  const_iterator end() const { return va.end(); }

};

struct ContianerB
{
  std::vector<B> vb;
  int additional_data;

  // Add const and non-const versions of begin() and end().

  using iterator = std::vector<B>::iterator;
  using const_iterator = std::vector<B>::const_iterator;

  iterator begin() { return vb.begin(); }
  iterator end() { return vb.end(); }

  const_iterator begin() const { return vb.begin(); }
  const_iterator end() const { return vb.end(); }

};

template <typename Container>
double sum(Container const& container)
{
   double res = 0;
   for (const auto &a: container) 
       res += a.x;
   return res;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Thank you, I was thinking about this. The only problem I have with it that an explicit relation of that ContainerB extends ContainerA is missing here, so I can not move this sum to ContainerA. Do you think this problem can be solved as well? – Solon Aug 18 '20 at 16:38
  • 2
    @Solon, that's an artificial constraint you are imposing on yourself. Prefer to use non-member functions whenever you can. – R Sahu Aug 18 '20 at 16:39
2

how to express that ContainerB "extends" ContainerA

At the moment it doesn't. You could easily change that however:

struct ContainerA{
  std::vector<A> va; 
};
struct ContianerB{
  std::vector<B> va;
  int additional_data;
};

Static polymorphism works best when the names match.

And then

template <typename Container>
double sum(Container const& container) 
/* requires AVector<Container> */
{
   double res = 0;
   for (const auto &a: container.va) 
       res += a.x;
   return res;
}

With C++20 concepts for nicer validation

template<typename T> concept ARange = 
    std::range<T>
 && std::derived_from<T::value_type, A>;
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Thank you for your answer. What do you think about strong in ContainerB only axially data (vector of additional_data). This way I can explicitly extend ContainerA with ContainerB ```struct ContianerB : public ContainerA{ std::vector additional_data; int additional_data; };``` – Solon Aug 19 '20 at 16:30
  • @Solon My `ContianerB` can be substituted for a (readonly) `ContainerA`, without inheriting it, whilst also still holding bona-fide `B`s – Caleth Aug 19 '20 at 16:35
  • I would like to store a reference to ContainerA (or ContainerB) in another thread to observe changes (in ContainerA.va) – Solon Aug 20 '20 at 08:57
1

I think it's impossible to static_cast a Derived reference of a value to it's Base type reference in a getIdentifer method as shown implicitly in Steve Jessop's covariance example, because then you'd be creating a "temporary reference", but I might be wrong. So instead I made a method (called getElement) that returns a possibly casted pointer, which will definitely exist.

Because you might really want to have the explicit relation between ContainerA and ContainerB (in case of duplicated code), I wrote a class-based example on how you could implement it. Else, using the templated function is probably the way to go.

If you are using structs, you don't need the public, protected and private labels, nor setters for the variables, but you of course do need some getIdentifier method to access B type elements.

Here is a demonstrating program:

A

#pragma once
#include <iostream>

class A
{
public:
    A(const int& data);

    friend std::ostream& operator<<(std::ostream& s, const A& a);
protected:
    virtual std::ostream& toStream(std::ostream& s) const;
private:
    int m_data;
};

A::A(const int& data)
    : m_data{ data }
{
}

std::ostream& A::toStream(std::ostream& s) const
{
    return s << m_data;
}

std::ostream& operator<<(std::ostream& s, const A& a)
{
    return a.toStream(s);
}

B

#pragma once
#include "A.h"

class B : public A
{
public:
    B(const int& data, const int& additionalData);
    void setAdditionalData(int additionalData);
protected:
    virtual std::ostream& toStream(std::ostream& s) const;
private:
    int m_additinalData;
};

B::B(const int& data, const int& additionalData)
    : A{ data },
    m_additinalData{ additionalData }
{
}

void B::setAdditionalData(int additionalData)
{
    m_additinalData = additionalData;
}

std::ostream& B::toStream(std::ostream& s) const
{
    A::toStream(s);
    return s << '\t' << m_additinalData;
}

ContainerA

#pragma once
#include "A.h"

#include <vector>

class ContainerA
{
public:
    void push(A* a);
    size_t getSize() const;
    virtual A* getElement(const int& index);
    virtual A* operator[](const int& index);
protected:
    std::vector<A*> m_As;
};

void ContainerA::push(A* a)
{
    m_As.push_back(a);
}

A* ContainerA::getElement(const int& index)
{
    return m_As[index];
}

A* ContainerA::operator[](const int& index)
{
    return m_As[index];
}

size_t ContainerA::getSize() const
{
    return m_As.size();
}

ContainerB

#pragma once
#include "ContainerA.h"
#include "B.h" // The compiler should be able to tell that B is a subclass of A

class ContainerB : public ContainerA
{
public:
    B* getElement(const int& index) override;
    B* operator[](const int& index) override;
private:
    int additional_data;
};

B* ContainerB::getElement(const int& index)
{
    return static_cast<B*>(m_As[index]);
}

B* ContainerB::operator[](const int& index)
{
    return static_cast<B*>(m_As[index]);
}

main.cpp

#include "ContainerB.h"

int main()
{
    B b1{ 4, -1 };
    B b2{ 5, -1 };
    B b3{ 6, -1 };

    ContainerB contB{};
    contB.push(&b1);
    contB.push(&b2);
    contB.push(&b3);

    B* b{ contB.getElement(0) };
    b->setAdditionalData(0);

    size_t size{ contB.getSize() };
    for (int i{ 0 }; i < size; ++i) {
        std::cout << *contB.getElement(i) << std::endl;
        std::cout << *contB[i] << std::endl;
    }
}

Output

4       0
4       0
5       -1
5       -1
6       -1
6       -1

Now you can pass ContainerB to a function / method that expects a ContainerA without having to store redundant data.

  • thank you for the great answer. One thing I didn't say is that Its static runtime polymorphism is too expensive for me in this example. (mostly because those function will be called millions of times and in your case can not be inlined) – Solon Aug 19 '20 at 10:42
0

Consider using static polymorphism (templates).

(code not tested)

auto const& inner_vector(ContainerA& ca){return ca.va;}
auto const& inner_vector(ContainerB& cb){return cb.vb;}

template<class TContainer>
double sum(TContainer &c){
   double res = 0;
   for (const auto &a: inner_vector(c)) 
       res += a.x;
   return res;
}
alfC
  • 14,261
  • 4
  • 67
  • 118