Reputation: 1
Say I have two method to process values, CumSum and CumProd; I have a string to determine which method will be used, choice. They are all inside a class, and choice is a class argument. CumSum and CumProd is a class to do the cumulative computation for every vector.
#include <vector>
#include <iostream>
#include <cmath>
struct CumSum {
public:
int result = 0;
void Push(int& x) {
result += x;
}
int ReturnVal()
{
return result;
}
};
struct CumProd {
public:
int result = 1;
void Push(int& x) {
result *= x;
}
int ReturnVal()
{
return result;
}
};
class Example
{
public:
const int vector_size = 10;
std::string choice;
std::vector<CumSum * if choice == "A" else CumProd *> methods;
public:
Example(std::string method)
{
choice = method;
for (size_t ii = 0; ii < vector_size; ++ii)
{
methods.push_back(choice == "A" ? new CumSum() : new CumProd());
}
}
std::vector<int> process(std::vector<float> value)
{
std::vector<int> result(value.size(), NAN);
for (size_t ii = 0; ii < value.size(); ++ii)
{
methods[ii]->Push(value[ii]);
result[ii] = methods[ii] -> ReturnVal();
}
return result;
}
};
The problem is that how should I properly declare the method vector
std::vector<CumSum * if choice == "A" else CumProd *> methods;
Thanks a lot!
updated the demo code a little bit to make the problem clear.
Upvotes: 0
Views: 130
Reputation: 62694
If all you need is one member function, you can use std::function
to do the virtual dispatch for you.
std::function<int(int)> methodA()
{
return [method = MethodA{}](int value) {
method.push(value);
return method.returnValue();
};
}
std::function<int(int)> methodB()
{
return [method = MethodB{}](int value) {
method.push(value);
return method.returnValue();
};
}
class Example
{
public:
const int vector_size = 10;
std::vector<std::function<int(int)>> methods;
public:
Example(std::string method)
{
for (size_t ii = 0; ii < vector_size; ++ii)
{
methods.push_back(choice == "A" ? methodA() : methodB());
}
}
void process(std::vector<float> value)
{
for (size_t ii = 0; ii < value.size(); ++ii)
{
results[ii] = methods[ii](value[ii]);
}
}
};
If you are able to modify MethodA
and MethodB
, you can simplify that further by renaming push
:
class Sum
{
public:
int operator()(int X)
{
result += x;
return result;
}
};
class Product
{
public:
int operator()(int X)
{
result *= x;
return result;
}
};
class Example
{
public:
const int vector_size = 10;
std::vector<std::function<int(int)>> methods;
public:
Example(std::string method)
{
for (size_t ii = 0; ii < vector_size; ++ii)
{
if (choice == "A") {
methods.push_back(MethodA{});
} else {
methods.push_back(MethodB{});
}
}
}
void process(std::vector<float> value)
{
for (size_t ii = 0; ii < value.size(); ++ii)
{
results[ii] = methods[ii](value[ii]);
}
}
};
You could also move the function into a Cumulative
class.
struct CumulativeOp {
int result;
std::function<int(int, int)> op;
void Push(int x) {
result = op(result, x);
}
int ReturnVal()
{
return result;
}
};
CumulativeOp sum() {
return { 0, std::plus{} };
}
CumulativeOp prod() {
return { 1, std::times{} };
}
class Example
{
public:
const int vector_size = 10;
std::vector<CumulativeOp> methods;
public:
Example(std::string method)
: methods(vector_size, choice == "A" ? sum() : prod())
{
}
void process(std::vector<float> value)
{
for (size_t ii = 0; ii < value.size(); ++ii)
{
methods[ii].push(value[ii]);
results[ii] = methods[ii].ReturnVal();
}
}
};
As an aside, you can't call a function expecting an int &
by passing a float &
, which is what process
is trying to do. You will have to decide you have int
s or float
s.
Upvotes: 0
Reputation: 5503
In this case you could solve this either with polymorphism or with a std::variant
. This will allow you to store either an instance of MethodA
or MethodB
in the vector
.
#include <vector>
#include <memory>
#include <iostream>
struct Base {
virtual void push(int& x) const = 0;
virtual ~Base() = default;
};
struct MethodA : public Base {
void push(int& x) const override {
x += 1;
}
};
struct MethodB : public Base {
void push(int& x) const override {
x += 20;
}
};
int main() {
std::vector<std::unique_ptr<Base>> methods{};
methods.push_back(std::make_unique<MethodA>());
methods.push_back(std::make_unique<MethodB>());
int initial = 1;
for (const auto& method : methods) {
method->push(initial);
std::cout << initial << '\n';
}
}
If you use a std::variant take a look at the documentation for it. There are other free functions available for querying what type is currently held and for getting the instance (like std::get
). Also, the visitor pattern can be used to clean this up further.
#include <vector>
#include <iostream>
#include <variant>
struct MethodA {
void push(int& x) const {
x += 1;
}
};
struct MethodB {
void push(int& x) const {
x += 20;
}
};
using MethodTypes = std::variant<MethodA, MethodB>;
int main() {
std::vector<MethodTypes> methods{};
methods.push_back(MethodA{});
methods.push_back(MethodB{});
int initial = 1;
for (const auto& method : methods) {
if (auto* a = std::get_if<MethodA>(&method)) {
a->push(initial);
std::cout << initial << '\n';
}
else if (auto* b = std::get_if<MethodB>(&method)) {
b->push(initial);
std::cout << initial << '\n';
}
}
}
Upvotes: 1
Reputation: 1494
This is a classic issue with C++. As one commenter mentioned, C++ is statically typed so all types of things must be known at compile-time. However, there are two typical ways around that.
This is a more Java-like solution and will be every so very slightly slower at run-time. I am using the smart pointer type std::unique_ptr
here so there is no manual memory deallocation needed.
#include <memory>
#include <vector>
#include <string>
class MethodBase {
public:
virtual void push(int&) = 0;
virtual ~MethodBase() { }
};
class MethodA :public MethodBase {
public:
void push(int& X) override {
X += 1;
}
};
// similar for MethodB
void make_vector(std::string choice) {
std::vector<std::unique_ptr<MethodBase>> methods;
if (choice == "A") {
methods.push_back(std::make_unique<MethodA>());
}
else { /* ... deal with other method choices ... */ }
// now you can call anything in the MethodBase interface
int x = 100;
methods.front()->push(x);
}
C++ templates allow you to easily compile multiple "copies" of any function or class, on-demand as needed. Then you would need some conditional statement elsewhere to decide which "copy" to actually use.
This way will be ever so slightly faster than polymorphism at runtime, with no dynamic memory used, but it's also less flexible because you don't really get one variable that can be a vector of either variety.
class MethodA {
public:
void push(int& X) {
X += 1;
}
};
// similar for MethodB
template <typename C>
void make_vector() {
std::vector<C> methods;
// populate the vector
methods.emplace_back(); // constructs a new method object in-place
// now use it...
int x = 100;
methods.front().push(x);
}
int main() {
std::string choice;
std::cin >> choice;
if (choice == "A") {
make_vector<MethodA>();
}
else { /* other cases for MethodB etc */ }
}
Upvotes: 0