I am attempting to call a templated function on the value but I want to call the templated version of the function that is from the potentially derived type. It is being determined that the type is Dog
during the typeid
but any attempt to cast is coming up short. How can I call the derived types templated WriteData
function?
About the example
WriteData
function is templated even though the value is not used in this example as I made a small example.Array
parameter will be an Animal
.#include <iostream>
using namespace std;
struct Writer {
void Write(char* data) {
cout << data << endl;
}
};
template<typename Type>
struct Array {
Type Data[2];
template<typename Formatter>
void WriteData(Formatter formatter) {
cout << "Data[0](typeid): " << typeid(*Data[0]).name() << endl;
cout << "Data[1](typeid): " << typeid(*Data[1]).name() << endl;
Data[0]->WriteData(formatter); // "Animal is doing something"
Data[1]->WriteData(formatter); // "Dog is doing something"
auto data1cast = reinterpret_cast<decltype(Data[1])>(Data[1]);
cout << "data1cast(typeidformatter " << typeid(*data1cast).name() << endl; // data1cast(typeid): class Dog
(*data1cast).WriteData(formatter); // "Animal is doing something"
((Dog*)Data[1])->WriteData(formatter); // "Dog is doing something"
}
};
struct Animal {
virtual ~Animal() = default;
template<typename Formatter>
void WriteData(Formatter formatter) {
formatter.Write("Animal is doing something");
}
};
struct Dog : Animal {
template<typename Formatter>
void WriteData(Formatter formatter) {
formatter.Write("Dog is doing something");
}
};
int main(void) {
Array<Animal*> arr;
arr.Data[0] = new Animal();
arr.Data[1] = new Dog();
Writer writer;
arr.WriteData(writer);
system("PAUSE");
return 0;
}
Output
Data[0](typeid): struct Animal
Data[1](typeid): struct Dog
Animal is doing something
Animal is doing something
data1cast(typeid): struct Dog
Animal is doing something
Dog is doing something
Templates and virtuals cannot be mixed:
template<typename Formatter>
virtual void WriteData(Formatter formatter);
would be an invalid declaration.
A member function template cannot be virtual, and a member function template in a derived class cannot override a virtual member function from the base class.
You have several options here depending on what flexibility you want to achieve and what other constraints you have. For example:
1.
You can introduce an abstract class Formatter
:
struct Formatter {
virtual ~Formatter() = default;
virtual void Write(const std::string&) = 0;
};
struct Writer : Formatter {
void Write(const std::string& data) override {
std::cout << data << std::endl;
}
};
and make WriteData
a non-template:
struct Animal {
virtual ~Animal() = default;
virtual void WriteData(Formatter& formatter) {
formatter.Write("Animal is doing something");
}
};
struct Dog : Animal {
void WriteData(Formatter& formatter) override {
formatter.Write("Dog is doing something");
}
};
2.
Make WriteData
a non-virtual template that calls virtual member functions to get information that should be passed to Formatter
:
struct Writer {
template<class T>
void Write(const T& data) {
std::cout << data << std::endl;
}
};
struct Animal {
virtual ~Animal() = default;
template<class Formatter>
void WriteData(Formatter formatter) {
formatter.Write(MyName() + " is doing something");
}
virtual std::string MyName() {
return "Animal";
}
};
struct Dog : Animal{
virtual std::string MyName() {
return "Dog";
}
};
3.
Don't use virtuals at all, dispatch WriteData
call at compile-time. Instead of array use std::tuple
to store distinct types:
struct Writer {
template<class T>
void Write(const T& data) {
std::cout << data << std::endl;
}
};
template<typename... Types>
struct Array {
std::tuple<Types...> Data;
Array(Types... data) : Data(std::move(data)...) {}
template<typename Formatter>
void WriteData(Formatter formatter) {
std::get<0>(Data).WriteData(formatter);
std::get<1>(Data).WriteData(formatter);
}
};
struct Animal {
template<typename Formatter>
void WriteData(Formatter formatter) {
formatter.Write("Animal is doing something");
}
};
struct Dog { // can be derived from Animal, but here it doesn't matter
template<typename Formatter>
void WriteData(Formatter formatter) {
formatter.Write("Dog is doing something");
}
};
int main() {
Array<Animal, Dog> arr{Animal(), Dog()}; // <...> can be omitted in C++17
Writer writer;
arr.WriteData(writer);
}
4.
You can use dynamic_cast
inside if-else
branches:
struct Writer {
template<class T>
void Write(const T& data) {
std::cout << data << std::endl;
}
};
struct Animal {
virtual ~Animal() = default;
template<typename Formatter>
void WriteData(Formatter formatter) {
formatter.Write("Animal is doing something");
}
};
struct Dog : Animal {
template<typename Formatter>
void WriteData(Formatter formatter) {
formatter.Write("Dog is doing something");
}
};
template<typename Type>
struct Array {
Type Data[2];
template<typename Formatter>
void WriteData(Formatter formatter) {
for (auto d : Data) {
if (auto dog = dynamic_cast<Dog*>(d))
dog->WriteData(formatter);
else
d->WriteData(formatter);
}
}
};
This design is pretty bad. Each time you modify the hierarchy of animals, you have to update Array::WriteData
.