Reputation: 507
Assume I have the following class A
which is passed around through different function calls and wrappers.
class A{
std::vector<int> a;
public:
int getSize const {return a.size();}
int getVal(int i) const {return a[i];}
// other private and public members and functions
}
Now for some reason I need the same class but with a double vector. I cannot templatize this class, because there are many function signatures that I cannot change. What is suggested is to rename A
to A0
, templatize that, create new A
containing A0<int>
, and A0<double>
as follows:
template <typename T>
class A0{
std::vector<T> a;
public:
int getSize const {return a.size();}
T getVal(int i) const {return a[i];}
// other private and public members and functions
}
class A{
// only one of the following will be initialized in the constructor and the other one will be null.
std::shared_ptr<A0<int>> iA;
std::shared_ptr<A0<double>> dA;
// also the following flag will be set in the constructor
bool isInt;
}
This is the question: If I want to make minimum changes in different places of the code that previously accessed, changed, or just passed around the instances of class A
, what shall be done? For example, consider this in a different part of the old code:
A x;
int n = x.getSize();
Is there a way to keep that old code without implementing a method getSize()
inside the new A
class that would contain an if-conditional statement and return either iA->getSize()
or dA->getSize()
based on isInt
? Is there a smart way to do this?
Is there any other suggestions for achieving the goal of minimum modifications in different parts of the code that use (mostly pass around) the old A
?
}
Upvotes: 0
Views: 81
Reputation: 22152
If this is only about passing on the object (or a reference to it) and not actually using any member of the object, then you can simply use a std::variant
:
using A = std::variant<A0<int>, A0<double>>;
At the point where members are actually used, use std::visit
to determine the type and act on it.
If passing happens only by-reference, then making the A
an empty base class of A0<T>
will also work. You can then cast back to the real type using static_cast<A0<int>&>(...)
or static_cast<A0<double>&>(...)
to use the members at the destination. You must however assure that you cast to the actual type of the passed object, otherwise you have undefined behavior.
An alternative is dynamic_cast
instead of static_cast
, which will throw or return a null pointer if the types don't match. But that requires the base class A
to have a virtual method to be polymorphic.
Upvotes: 1
Reputation: 1282
std::variant
might be what you need. Create a buffer
class and forward all the container operations to it. There is no need to change too much in your class A
, but you should mimic std::vector<T>
in buffer
, i implemented size()
and const subscript operator
. Here is the basic demonstration.
#include <variant>
#include <vector>
#include <iostream>
struct store_as_int {};
struct store_as_double {};
class buffer {
public:
buffer( store_as_int ) : data { std::vector<int>{} } {
std::cout << "I am storing numbers as int" << std::endl;
}
buffer( store_as_double ) : data { std::vector<double>{} } {
std::cout << "I am storing numbers as double" << std::endl;
}
[[nodiscard]] std::size_t size() const noexcept {
std::size_t s;
std::visit( [ &s ]( auto&& arg ) {
s = arg.size();
} , data );
return s;
}
[[nodiscard]] double operator[]( std::size_t idx ) const {
double s;
std::visit( [ &s , idx ]( auto&& arg ) {
s = arg[ idx ];
} , data );
return s;
}
private:
std::variant< std::vector<int> , std::vector<double> > data;
};
class A{
buffer a;
public:
A() : a { store_as_int{} } {}
A( store_as_double ) : a { store_as_double {} } { }
int getSize() const { return a.size(); }
int getVal(int i) const { return a[i]; }
};
int main()
{
A x;
A y { store_as_double{} };
int n = x.getSize();
int t = x.getSize();
std::cout << n << std::endl;
std::cout << t << std::endl;
}
Output :
I am storing numbers as int
I am storing numbers as double
0
0
Upvotes: 1