Reputation: 362
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?
Upvotes: 1
Views: 208
Reputation: 16242
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;
}
Upvotes: 0
Reputation:
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:
#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);
}
#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;
}
#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();
}
#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]);
}
#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;
}
}
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.
Upvotes: 1
Reputation: 62576
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>;
Upvotes: 2
Reputation: 206567
One way to use sum
with either a ContainerA
or ContainerB
.
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;
}
Upvotes: 2