I have created a minimal example based on my complicated project that is using boost:
template<typename T>
struct Data {
T attribute;
void print() {}
};
typedef Data<int> IntDataType;
typedef Data<float> FloatDataType;
std::shared_ptr<IntDataType> make_int_data() {
return std::make_shared<IntDataType>();
}
std::shared_ptr<FloatDataType> make_float_data() {
return std::make_shared<FloatDataType>();
}
std::any data;
void create_data(bool useFloat) {
if (useFloat)
data = make_float_data();
else
data = make_int_data();
}
auto get_data() {
try {
auto c = std::any_cast<FloatDataType>(data);
return c;
}
catch (std::bad_any_cast const& ex) {
std::cout << ex.what() << '\n';
}
auto c = std::any_cast<IntDataType>(data);
return c;
}
int main() {
create_data(true);
get_data().print();
return 0;
}
I have Data type that is based on a templated argument and I am created two Data types based on that: IntDataType and FloatDataType. I have a global variable std::any that could contain both of my types and sure I could try to cast to one type and to another.
The problem is that I am using these casts in a lot of places and the code is a mess. And as a result, I want to create a function that could return any of these types and make a simple call of method that is common for both (int our case print()). In my case this is a get_data() but it gives an error:
inconsistent deduction for auto return type: Data<float> and then Data<int>
I could not change the types because they are part of the boost library in my case. Is there a way to do it using modern C++? Will appreciate any help.
std::variant<std::shared_ptr<IntDataType>, std::shared_ptr<FloatDataType>> data;
void create_data(bool useFloat) {
if (useFloat)
data = make_float_data();
else
data = make_int_data();
}
std::visit([](auto& elem) { elem->print(); }, data);
std::variant
can be used in more complex cases, I wrote about it once in my blog:
Design Patterns: RunTime Reflection – C++
Edit:
An easier approach (as @PepijnKramer mentioned that is possible in that case) would be a common non-templated interface. Notice that it won't always be the case, and std::variant
would usually be the easiest solution.
struct DataI {
virtual ~DataI() = default;
virtual void print() {};
};
template<typename T>
struct Data : DataI {
T attribute;
};
std::shared_ptr<DataI> data;
void create_data(bool useFloat) {
if (useFloat)
data = make_float_data();
else
data = make_int_data();
}
Another approach, in case that you don't want to use virtual tables, is a common template base class collection, introduced by Sean Parent. I also wrote about it: