I learned the concept of polymorphism and I am trying to use the concept in order to create a map called "pipeline" that takes a vector of inputs (the idea is that these could be different data structure) and applies to the input a function (the function could also be selected differently in different use cases) and which produce a vector of results (which also change from case to case). My idea was to try the following
#include <iostream>
#include <vector>
using namespace std;
struct input
{
};
struct intInput : public input
{
intInput(int x) :x(x) {}
int x;
};
struct result
{
};
struct intResult : public result
{
intResult(int y) :y(y) {}
int y;
};
vector<result> pipeline(vector<input> x, result(*g)(input))
{
std::vector<result> res;
for (size_t i = 0; i < x.size(); i )
{
res.push_back((*g)(x[i]));
}
return res;
}
intResult doubling(intInput i)
{
intResult res(2 * i.x);
return res;
}
void printing(input i)
{
cout << "hi" << endl;
}
int main()
{
vector<intInput> v{ 1,2,3,4 };
pipeline(v, doubling);
}
but I get the compilation error
'std::vector<result,std::allocator<result>> pipeline(std::vector<input,std::allocator<input>>,result (__cdecl *)(input))': cannot convert argument 1 from 'std::vector<intInput,std::allocator<intInput>>' to 'std::vector<input,std::allocator<input>>'
Is there a way to fix this?
CodePudding user response:
vector<input>
and vector<intInput>
aren't polymorphic and will never be - vector<input>
knows that each entry is 1 byte long (the minimum size) and vector<intInput>
knows that each entry is 4 bytes long.
However, you could achieve what you want by making pipeline
a template, for example:
template<typename MyResult, typename MyInput>
vector<MyResult> pipeline(vector<MyInput> x, MyResult(*g)(MyInput))
{
std::vector<MyResult> res;
for (size_t i = 0; i < x.size(); i )
{
res.push_back((*g)(x[i]));
}
return res;
}
No inheritance is required. You can delete the classes input
and result
. The compiler will be able to generate variants of this function as needed, like these ones:
vector<intResult> pipeline<intInput, intResult>(vector<intInput> x, intResult(*g)(intInput))
vector<int> pipeline<int, int>(vector<int> x, int(*g)(int))
vector<string> pipeline<float, string>(vector<float> x, string(*g)(float))
You can call the function as pipeline<intInput, intResult>(v, doubling);
, but you can also leave out the <>
part when the compiler is able to work it out by itself, so you can call pipeline(v, doubling);
Note that if you declare this function* in a header file, the definition should also be in the same header file and not in a .cpp file. That's because every time the compiler sees you call the function, it has to create the correct version of the function for that call, therefore it has to know what code is in the function.
* technically it is a function template, not a function
Inheritance polymorphism in C only works with pointers or references. That's because all pointers are the same size. Code that handles a input*
doesn't have to care there are actually some extra variables after the end of the input
that it doesn't know about. (Unless it uses pointer arithmetic! Pointer arithmetic isn't polymorphic)
You could have a vector of pointers (vector<input*>
) but this is usually a little bit unwieldy. Or you could make some thing that returns pointers: (done here with pointers throughout, but references also work)
struct inputGetter {
virtual input* getInputPointer(int index) = 0;
virtual size_t size() = 0;
};
struct intInputVectorGetter : public inputGetter {
vector<intInput>* theVector;
intInputVectorGetter(vector<intInput>* theVector) {
this->theVector = theVector;
}
input* getInputPointer(int index) override {
return &theVector[index];
}
size_t size() override {
return theVector.size();
}
};
// make a resultSetter, too
void pipeline(resultSetter* y, inputGetter* x, void(*g)(output*, input*)) {
y->clear();
y->setSize(x->size());
for (size_t i = 0; i < x->size(); i )
{
(*g)(y->getResultPointer(i), x->getInputPointer(i));
}
}
As you can see it is substantially uglier in order to prevent the pipeline
function from handling any of the actual result objects - only the pointers. For this pipeline
function, the template solution really is better.