Dynamic class type from file data - c++

I have some JSON files in which I define objects of various types. The types are given as a field within the objects. I want to load the file and for each JSON object, create a new class of that type and pass the rest of the JSON data to its constructor.
The issue is that I'd rather not have a huge case statement matching the type and creating an object of that type. Here are some of the possibilities I've considered:
Reflection. I don't know too much about it, but my understanding is that it might allow me to create a class in this manner. While I'm aware C++ doesn't provide this capability natively, I've seen a few libraries such as this one that might provide such functionality.
Create an enum of class types. Create a template function that takes a type parameter from this enum and creates an object of that type. Use something like smart_enum to convert the string field.
Option 2 seems like a good one but I haven't been able to get this working. I've done extensive googling, but no luck. Does anyone know how I might go about doing this, or if there is a better option which I have not considered? Apologies if this has been answered elsewhere, perhaps under a term which I do not know; I have spent quite a lot of time trying to solve this problem and had no luck.
Please let me know if I can provide any additional information, and thank you.
Edit: here's an example of what I've tried to get option 2 working.
#include <iostream>
#include <string>
enum class Animals {
Dog,
Cat
};
class Dog {
public:
std::string sound{"woof"};
};
class Cat {
public:
std::string sound{"meow"};
};
template<Animals animal> void make_sound() {
new animal();
cout << animal.sound << endl;
}
int main() {
make_sound<Animals::Dog>();
make_sound<Animals::Cat>();
std::exit(1);
}

There are a number of C++ JSON libraries that support mapping polymorphic or std::variant types based on some type selection strategy, which could rely on a type marker (e.g. "cat", "dog"), or alternatively the presence or absence of members. Lacking reflection, such libraries rely on traits. Typically the library provides built-in traits specializations for standard library types such as std::chrono::duration and std::vector, and supports custom specializations for user types. Some libraries offer convenience macros that can be used to generate the code for custom specializations.
The library ThorsSerializer has an example of encoding/decoding JSON for polymorphic types.
The library jsoncons has examples of encoding/decoding JSON for polymorphic types and the std::variant type

As noted in the comments, #1 is out, C++ lacks reflection (until P0194 gets adopted).
#2 still requires a big ol' switch block because you're still have to switch on a run-time type ID.
So, I'll propose #3: use a template to generate all those case statements you don't want to have to write (well, a map anyway).
This is the final code, which uses JSON for Modern C++ library for JSON parsing since that's the one that's available from godbolt :).
template <typename T, typename... Args>
struct option {
using type = T;
static_assert(std::is_constructible_v<T, Args...>, "constructor doesn't exist");
static T create(const nlohmann::json& json) {
return create_impl(json, std::index_sequence_for<Args...>{});
}
template <size_t... Is>
static T create_impl(const nlohmann::json& json, std::index_sequence<Is...>) {
return { json[Is].get<Args>()... };
}
};
template <typename...>
struct to_string {
using type = std::string_view;
};
template <typename... Options>
struct factory_builder {
using variant = std::variant<typename Options::type...>;
factory_builder(typename to_string<Options>::type... names)
: map { std::pair<std::string, std::function<variant(const nlohmann::json&)>> { names, [](const nlohmann::json& json) -> variant { return Options::create(json); } }... }
{ }
variant operator ()(const nlohmann::json& json) {
return map[json["type"].get<std::string>()](json["args"]);
}
std::map<std::string, std::function<variant(const nlohmann::json&)>> map;
};
Usage:
using factory_t = factory_builder<
option<Dog, double, std::string>, // Dog & its constructor argument types
option<Cat, int> // Cat & its constructor argument types
>;
factory_t factory("Dog", "Cat"); // String type identifiers for each option
auto my_object = factory( /* ... your JSON */ );
It assumes the JSON takes this form, where "type" is one of the string identifiers passed to the factory_builder constructor, and "args" is a list of arguments:
{
"type": "TheTypeIdentifier",
"args": [42, "whatever", false]
}
Demo: https://godbolt.org/z/3qfP9G
That's a lot of code, so let's break it down. First problem you need to solve is how to actually have a variable that can be more than one type, since C++ is strongly typed. C++17 provides std::variant for this, so we'll use that.
using result = std::variant<Dog, Cat>;
result example_result = Dog {};
example_result = Cat {};
Next, you need a way to generically describe, at compile time, how to construct an object: I used a simple struct with a template argument for the type, and a variable number of template arguments for the types going into that constructor:
template <typename T, typename... Args>
struct options;
Given a option<T, Args...>, how do you take a JSON array and pass those items to the constructor? With the nlohmann library, if you have a parsed JSON array called my_array and want to get index n and store it in an object of type T:
my_array[n].get<T>(); // accesses the array, converts, and returns a T&
To do that generically, I took the parameter pack of arguments and converted it into a parameter pack of increasing integers (0, 1, 2...) using std::index_sequence. Then I expanded the two parameter packs into T and n in the example above. It was convenient to put all this inside a static method of option<T, Args...>:
template <typename T, typename... Args>
struct option {
/* ... */
static T create(const nlohmann::json& json) {
return create_impl(json, std::index_sequence_for<Args...>{});
}
template <size_t... Is>
static T create_impl(const nlohmann::json& json, std::index_sequence<Is...>) {
return { json[Is].get<Args>()... };
}
};
That solves extracting arguments and calling a constructor for one type generically. Next problem is, how do you generate a function that switches on the type name and calls one of those Option<T, ...>::create functions?
For this solution, I used a map from strings to an std::function that takes JSON in and outputs our variant type:
template <typename... Options>
struct factory_builder {
// note: using a typedef "type" in options<T, Args...> that just points to T
using variant = std::variant<typename Options::type...>;
factory_builder(/* one string for each type */)
{
// TODO: populate map
}
variant operator ()(const nlohmann::json& json) {
return map[json["type"].get<std::string>()](json["args"]);
}
std::map<std::string, std::function<variant(const nlohmann::json&)>> map;
};
Now we just need to build that map. First, a detour: how do you ask for one string per type in a parameter pack? I used a helper type that takes a template argument and has a typedef that is always a string. Expand into that, and you get a parameter pack of string types:
template <typename...>
struct to_string {
using type = std::string_view;
};
Then, to populate the map, you can do that right from the initializer list:
using map_t = std::map<std::string, std::function<variant(const nlohmann::json&)>>;
factory_builder(...) : map {
typename map_t::value_type {
names,
[](const nlohmann::json& json) -> variant {
return Options::create(json);
}
}...
}
This is a little confusing, but it's expanding into something like this:
factory_builder(std::string dogName, std::string catName) : map {
std::pair<...> { dogName, [](auto& j) { return Dog(...); } },
std::pair<...> { catName, [](auto& j) { return Cat(...); } }
}
And that's it! Hope it helps.

Related

Iterate over 2 template parameter packs in parallel

I started to implement a very flexible Odometer. It may have several disks with even different amount of values on each different disk. And, as an extension, even the data type of values on each of the single disks could be different.
All this shall be implemented with one class. The number of template parameters defines the behavior of the class.
1 template parameter: Like Odomoter<int> shall result in an Odometer having int values on each disk. The resulting internal data type will be a std::vector<std::vector<int>>
2 or more template parameters: The number of template parameter will define the number of single disks of the Odometer. Each disk has the data type of the template parameter. In the case of Odometer<char, int, double>, this will result in a data type std::tuple<std::vector<char>, std::vector<int>, std::vector<double>>
Now, I want to add a variadic constructor, where I can add whatever data. Of course types and number of arguments must match. I omit the check for the moment and will add it later.
So, now I have a templatized variadic class and a variadic constructor. So, I have the parameter pack off the class and the parameter pack of the constructor.
Now I would need iterate over the elements of both parameter packs at the same time in parallel.
Please see the below code example for an illustration of the problem (I deleted most of the code in the class, to just show you the problem):
#include <vector>
#include <tuple>
#include <list>
#include <initializer_list>
template<typename...Ts>
struct Odometer {
static constexpr bool IsTuple = ((std::tuple_size<std::tuple<Ts...>>::value) > 1);
template<typename...Ts>
using Tuples = std::tuple<std::vector<Ts>...>;
template<typename...Ts>
using MyType = std::tuple_element_t<0, std::tuple<Ts...>>;
template<typename...Ts>
using Vectors = std::vector<std::vector<MyType<Ts...>>>;
template<typename...Ts>
using Disks = std::conditional<IsTuple, Tuples<Ts...>, Vectors<Ts...>>::type;
Disks<Ts...> disks{};
template <typename...Args>
Odometer(Args...args) {
if constexpr (IsTuple) {
// Here disk is a std::tuple<std::vector<char>, std::vector<int>, std::vector<double>>
([&] {
//std::vector<MyType<Ts...>> disk{}; // Does not work. Or would always be a std::vector<char>
if constexpr (std::ranges::range<Args>) {
//for (const auto& r : args) // Does not work
//disk.push_back(r); // Does not work
}
else {
//disk.push_back(args); // Does not work
} } (), ...);
}
else {
([&] {
disks.push_back({});
if constexpr (std::ranges::range<Args>) {
for (const auto& r : args)
disks.back().push_back(r);
}
else {
disks.back().push_back(args);
} } (), ...);
}
}
};
int main() {
Odometer<char, int, double> odo2('a', std::vector{1,2,3}, std::list{4.4, 5.5});
}
I can iterate over the parameter pack of the constructor using a fold expression. I could also use std::apply. But, I need to iterate also over the tuple elements of the "disks", defined by the class template parameters.
I do not want to use recursive templates.
So, I need to iterate of 2 parameter packs in parallel at the same time. How could this be done?
The only idea I have now is to use a helper class with a std::index_sequence, but I do not know.
Please be reminded. Check of number of elements in parameter packs and type will be done later.
The only idea I have now is to use a helper class with a
std::index_sequence, but I do not know.
You can use template lambda to expand index_sequence and get the corresponding tuple elements through std::get<Is>:
static_assert(sizeof...(Args) == sizeof...(Ts));
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
([&] {
auto& disk = std::get<Is>(disks);
if constexpr (std::ranges::range<Args>)
for (const auto& elem : args)
disk.push_back(elem);
else
disk.push_back(args);
} (), ...);
}(std::index_sequence_for<Args...>{});
Demo with reduced examples
You don't need to iterate manually, you just need a function to convert each argument into vector:
template <typename T, typename Arg>
std::vector<T> as_vector(Arg&& arg)
{
if constexpr (std::ranges::range<std::decay_t<Arg>>) {
return {std::begin(arg), std::end(arg)};
} else {
return {std::forward<Arg>(arg)};
}
}
and then your constructor is simply (Should be more complicated: SFINAE for nearly copy constructor with forwarding reference to avoid current copies):
template <typename...Args>
Odometer(Args... args) : disks{as_vector<Ts>(args)...} {}
Demo
Notice that as_vector<Ts>(args)... uses both packs with a single ..., so they should have same size.
Because you are handling the case where Ts... is a pack of one differently, it would be sensible to make that a specialisation.
Then you can expand both packs in one ... for the general case, and you only have one pack for the special case.
template <typename T, typename Arg>
std::vector<T> as_vector(Arg&& arg)
{
if constexpr (std::ranges::range<std::decay_t<Arg>>) {
return {std::begin(arg), std::end(arg)};
} else {
return {std::forward<Arg>(arg)};
}
}
template<typename...Ts>
struct Odometer {
std::tuple<std::vector<Ts>...> disks{};
template <typename...Args>
requires (sizeof...(Args) == sizeof...(Ts))
Odometer(Args&&... args) : disks{as_vector<Ts>(std::forward<Args>(args))...} {}
};
template<typename T>
struct Odometer<T> {
std::vector<std::vector<T>> disks{};
template <typename...Args>
Odometer(Args&&... args) : disks{as_vector<T>(std::forward<Args>(args))...} {}
};

How to rewrite variadic template of type T to specific (non-primitive) type

I have this code, which fills my row structure with string data for each column.
struct row_t
{
row_t()
{};
template<typename ...T>
row_t(const T& ...var)
{
append(var...);
}
template<typename T, typename ...Types>
void append(const T &var1, const Types& ...var2)
{
// here should be some kind of check, that number of arguments == COLUMN_COUNT
data.append_copy(var1);
append(var2...);
}
template<typename T>
void append(const T &var1)
{
data.append_copy(var1);
}
array_local_t<sstring_t, COLUMN_COUNT> data; // my implementation of arrays
};
and I can call is like this:
row_t tmp_row(a,b,c,d);
//with a, b, c, d being my sstring_t types
Now, as you might noticed, my data array is of type sstring_t. So If i tried to call this with a,b,c,d being int, I would not be able to compile the code -> I don't need or want the template functions to be of Typename T, but only sstring_t, because that is the only thing that makes sense in my case (so I dont want to change data array to type T and call it a day). I was only able to write the code like this. Can you help me to convert it to specific type only(sstring_t) using C++11 max?
After some additional searching I found this link Which proposes a simple, but good logic how to get what I want with just a slight modification of my code. (I modified it to use vector so I could test it outside of work)
struct row_t
{
row_t()
{};
template<typename ...Types>
row_t(const string &var1, const Types& ...var2)
{
// here should be some kind of check, that number of arguments == COLUMN_COUNT
append(var1, var2...);
}
template<typename ...Types>
void append(const string &var1, const Types& ...var2)
{
data.push_back(var1);
append(var2...);
}
void append(const string &var1)
{
data.push_back(var1);
}
vector<string> data;
};
With this code, the only options for compiler when you try to call row_t row("something",2,3) or row(some_type, other_type, third_type) etc.. Is try to convert the given arguments to string. Because it can pick append with one string as argument, or append with string as first argument and some "possibly" other arguments... which due to induction have to be string as well.
I might have not been clear enough with my original question. The goal was to modify the code in a way that only valid call of the row_t constructor would be with string arguments. The code provided in the question was not compilable for other types, but it was because of the data type of row.data being string not because the template would not allow it. The code in this answer would fail on template append functions if provided with non-string arguments, not on push to row.data of wrong type.

Hiding variadic template implementation

I have some 3rdParty library with a method like this:
bool Invoke(const char* method, Value* args, size_t nargs)
It takes an array of its inner type (convertible to any primitive c++ types) and arg count as its inner params.
In my code, I wrote some generalized helper to avoid manual creation and type convertion for each invoke:
template<class ... Args>
bool WrappedValue::Invoke(const char* method, Args&& ... args)
{
3rdParty::Value values[] =
{
3rdParty::Value(std::forward<Args>(args))...
}
return m_value.Invoke(method, values, sizeof ... (Args));
}
It works just fine, but now I should have 3rdParty code defined in my header files and lib connected directly to my main project.
Is it possible to hide implementation details and usage of this 3rd party library? (Use some kind of pimple idiom or proxy object for 3rdParty::Value ). I know that it's not possible to use virtual template methods in c++ to create a proxy or simply move template implementation to .cpp, so I am totally stuck with this problem.
Will be grateful for any help)
Sure. Simply write the equivalent of std::variant<int, double, char, every, other, primitive, type>.
Now your Invoke converts your args into an array (vector, span, whatever) of those variants.
Then you pass this array of variants to your internal Invoke method.
That internal invoke method then uses the equivalent of std::visit to generate a 3rdParty::Value from each of your variants.
Boost provides a boost::variant which would probably work.
You could also roll this by hand. By narrowly specifying your problem, you'd get away with something simpler than a std::variant. It would be more than a bit of work, however.
Another approach is this
template<class T> struct tag_t {constexpr tag_t(){}; using type=T;};
template<class T> constexpr tag_t<T> tag{};
template<class T, class F, class ... Args>
bool WrappedValue::Invoke(tag_t<T>, F&& f, const char* method, Args&& ... args)
{
T values[] = {
T(std::forward<Args>(args))...
};
return std::forward<F>(f)(method, values, sizeof...(Args));
}
which is simpler. Here you'd write:
bool r = Invoke( tag<3rdParty::Value>, [&](const char* method, 3rdParty::Value* values, std::size_t count) {
m_value.Invoke( method, values, count );
}, 3.14, 42, "hello world");
If you want to avoid exposing the 3rdParty API, you need some non-template method to pass the data. That inevitably would require some type-erasure mechanism (like std::any), which instead is exposed in your API.
So, yes you could do that, but then the 3rdParty Value is already a type erasure method and this would only pass the data from one type erasure to the next, creating additional overhead. Whether that price is worth paying only you can decide.
I somehow overlooked your remark that the arguments are all primitive. In this case, type erasure is much simpler and can be done via a tag+union like
struct erasure_of_primitive
{
enum { is_void=0, is_str=1, is_int=2, is_flt=3, is_ptr=4 }
int type = is_void;
union {
const char*s; // pointer to external C-string
int64_t i; // any integer
double d; // any floating point number
void*p; // any pointer
} u;
erasure_of_primitive() = default;
erasure_of_primitive(erasure_of_primitive&const) = default;
erasure_of_primitive&operator=(erasure_of_primitive&const) = default;
erasure_of_primitive(const char*str)
: type(is_str), u.s(str) {}
template<typename T>
erasure_of_primitive(T x, enable_if_t<is_integer<T>::value>* =0)
: type(is_int), u.i(x) {}
template<typename T>
erasure_of_primitive(T x, enable_if_t<is_floating_point<T>::value>* =0)
: type(is_flt), u.d(x) {}
template<typename T>
erasure_of_primitive(T*x)
: type(is_ptr), u.p(static_cast<void*>(x)) {}
};

Containers for different signature functions

I'm trying to programming in C++ a framework where the user can indicates a set of functions inside its program where he wants to apply a memoization strategy.
So let's suppose that we have 5 functions in our program f1...f5 and we want to avoid the (expensive) re-computation for the functions f1 and f3 if we already called them with the same input. Notice that each function can have different return and argument types.
I found this solution for the problem, but you can use only double and int.
MY SOLUTION
Ok I wrote this solution for my problem, but I don't know if it's efficient, typesafe or can be written in any more elegant way.
template <typename ReturnType, typename... Args>
function<ReturnType(Args...)> memoize(function<ReturnType(Args...)> func)
{
return ([=](Args... args) mutable {
static map<tuple<Args...>, ReturnType> cache;
tuple<Args...> t(args...);
auto result = cache.insert(make_pair(t, ReturnType{}));
if (result.second) {
// insertion succeeded so the value wasn't cached already
result.first->second = func(args...);
}
return result.first->second;
});
}
struct MultiMemoizator
{
map<string, boost::any> multiCache;
template <typename ReturnType, typename... Args>
void addFunction(string name, function < ReturnType(Args...)> func) {
function < ReturnType(Args...)> cachedFunc = memoize(func);
boost::any anyCachedFunc = cachedFunc;
auto result = multiCache.insert(pair<string, boost::any>(name,anyCachedFunc));
if (!result.second)
cout << "ERROR: key " + name + " was already inserted" << endl;
}
template <typename ReturnType, typename... Args>
ReturnType callFunction(string name, Args... args) {
auto it = multiCache.find(name);
if (it == multiCache.end())
throw KeyNotFound(name);
boost::any anyCachedFunc = it->second;
function < ReturnType(Args...)> cachedFunc = boost::any_cast<function<ReturnType(Args...)>> (anyCachedFunc);
return cachedFunc(args...);
}
};
And this is a possible main:
int main()
{
function<int(int)> intFun = [](int i) {return ++i; };
function<string(string)> stringFun = [](string s) {
return "Hello "+s;
};
MultiMemoizator mem;
mem.addFunction("intFun",intFun);
mem.addFunction("stringFun", stringFun);
try
{
cout << mem.callFunction<int, int>("intFun", 1)<<endl;//print 2
cout << mem.callFunction<string, string>("stringFun", " World!") << endl;//print Hello World!
cout << mem.callFunction<string, string>("TrumpIsADickHead", " World!") << endl;//KeyNotFound thrown
}
catch (boost::bad_any_cast e)
{
cout << "Bad function calling: "<<e.what()<<endl;
return 1;
}
catch (KeyNotFound e)
{
cout << e.what()<<endl;
return 1;
}
}
How about something like this:
template <typename result_t, typename... args_t>
class Memoizer
{
public:
typedef result_t (*function_t)(args_t...);
Memoizer(function_t func) : m_func(func) {}
result_t operator() (args_t... args)
{
auto args_tuple = make_tuple(args...);
auto it = m_results.find(args_tuple);
if (it != m_results.end())
return it->second;
result_t result = m_func(args...);
m_results.insert(make_pair(args_tuple, result));
return result;
}
protected:
function_t m_func;
map<tuple<args_t...>, result_t> m_results;
};
Usage is like this:
// could create make_memoizer like make_tuple to eliminate the template arguments
Memoizer<double, double> memo(fabs);
cout << memo(-123.456);
cout << memo(-123.456); // not recomputed
It's pretty hard to guess at how you're planning to use the functions, with or without memoisation, but for the container-of-various-function<>s aspect you just need a common base class:
#include <iostream>
#include <vector>
#include <functional>
struct Any_Function
{
virtual ~Any_Function() {}
};
template <typename Ret, typename... Args>
struct Function : Any_Function, std::function<Ret(Args...)>
{
template <typename T>
Function(T& f)
: std::function<Ret(Args...)>(f)
{ }
};
int main()
{
std::vector<Any_Function*> fun_vect;
auto* p = new Function<int, double, double, int> { [](double i, double j, int z) {
return int(i + j + z);
} };
fun_vect.push_back(p);
}
The problem with this is how to make it type-safe. Look at this code:
MultiMemoizator mm;
std::string name = "identity";
mm.addFunction(name, identity);
auto result = mm.callFunction(name, 1);
Is the last line correct? Does callFunction have the right number of parameters with the right types? And what is the return type?
The compiler has no way to know that: it has no way of understanding that name is "identity" and even if it did, no way to associate that with the type of the function. And this is not specific to C++, any statically-typed language is going to have the same problem.
One solution (which is basically the one given in Tony D's answer) is to tell the compiler the function signature when you call the function. And if you say it wrong, a runtime error occurs. That could look something like this (you only need to explicitly specify the return type, since the number and type of parameters is inferred):
auto result = mm.callFunction<int>(name, 1);
But this is inelegant and error-prone.
Depending on your exact requirements, what might work better is to use "smart" keys, instead of strings: the key has the function signature embedded in its type, so you don't have to worry about specifying it correctly. That could look something like:
Key<int(int)> identityKey;
mm.addFunction(identityKey, identity);
auto result = mm.callFunction(identityKey, 1);
This way, the types are checked at compile time (both for addFunction and callFunction), which should give you exactly what you want.
I haven't actually implemented this in C++, but I don't see any reason why it should be hard or impossible. Especially since doing something very similar in C# is simple.
you can use vector of functions with signature like void someFunction(void *r, ...) where r is a pointer to result and ... is variadic argument list. Warning: unpacking argument list is really inconvenient and looks more like a hack.
At first glance, how about defining a type that has template arguments that differ for each function, i.e.:
template <class RetType, class ArgType>
class AbstractFunction {
//etc.
}
have the AbstractFunction take a function pointer to the functions f1-f5 with template specializations different for each function. You can then have a generic run_memoized() function, either as a member function of AbstractFunction or a templated function that takes an AbstractFunction as an argument and maintains a memo as it runs it.
The hardest part will be if the functions f1-f5 have more than one argument, in which case you'll need to do some funky things with arglists as template parameters but I think C++14 has some features that might make this possible. An alternative is to rewrite f1-f5 so that they all take a single struct as an argument rather than multiple arguments.
EDIT: Having seen your problem 1, the problem you're running into is that you want to have a data structure whose values are memoized functions, each of which could have different arguments.
I, personally, would solve this just by making the data structure use void* to represent the individual memoized functions, and then in the callFunction() method use an unsafe type cast from void* to the templated MemoizedFunction type you need (you may need to allocate MemoizedFunctions with the "new" operator so that you can convert them to and from void*s.)
If the lack of type safety here irks you, good for you, in that case it may be a reasonable option just to make hand-written helper methods for each of f1-f5 and have callFunction() dispatch one of those functions based on the input string. This will let you use compile-time type checking.
EDIT #2: If you are going to use this approach, you need to change the API for callFunction() slightly so that callFunction has template args matching the return and argument types of the function, for example:
int result = callFunction<int, arglist(double, float)>("double_and_float_to_int", 3.5, 4);
and if the user of this API ever types the argument type or return types incorrectly when using callFunction... pray for their soul because things will explode in very ugly ways.
EDIT #3: You can to some extent do the type checking you need at runtime using std::type_info and storing the typeid() of the argument type and return type in your MemoizedFunction so that you can check whether the template arguments in callFunction() are correct before calling - so you can prevent the explosion above. But this will add a bit of overhead every time you call the function (you could wrap this in a IF_DEBUG_MODE macro to only add this overhead during testing and not in production.)

C++ How to create a heterogeneous container

I need to store a series of data-points in the form of (name, value), where the value could take different types.
I am trying to use a class template for each data-point. Then for each data-point I see, I want to create a new object and push it back into a vector. For each new type, I need to create a new class from the template first. But I can not store the objects created in any vector, since vectors expect the same type for all entries. The types I need to store can not be fitted in a inheritance hierarchy. They are unrelated. Also there can be more types created in future, and I do not want to change the storage service for each new type. Is there a way to create a heterogeneous container to store these entries?
Thank you!
C++17 and later.
std::any allows to hold any type, although it requires knowing the type that was stored to retrieve it.
If you have a set of known types, however, you may prefer std::variant:
using variant_type = std::variant<Foo, Bar, Joe>;
int func(variant_type const& v) // not template
{
auto const visitor = [](auto const& t)
{
if constexpr (std::is_same_v<Foo const&, decltype(t)>)
{
return t.fooish();
}
else
{
return t.barjoeish();
}
};
return std::visit(visitor, v);
}
A useful trick for quickly defining visitors:
template <typename... Ts> struct overload : Ts...
{
overload(Ts... aFns) : Ts(aFns)... {}
using Ts::operator()...;
};
template <typename... Ts> overload(Ts...) -> overload<Ts...>;
// Used as
auto const visitor = overload(
[](Foo const& foo) { return foo.fooish(); },
[](auto const& other) { return other.joebarish(); }
);
return std::visit(visitor, variant);
Pre-C++17.
boost::any has already been recommended, however it's for anything, so you can't expect much from it.
If you know the various types ahead of time, you're better using boost::variant.
typedef boost::variant<Foo, Bar, Joe> variant_type;
struct Print: boost::static_visitor<>
{
void operator()(Foo const& f) const { f.print(std::cout); }
template <class T>
void operator()(T const& t) const { std::cout << t << '\n'; }
};
void func(variant_type const& v) // not template
{
boost::apply_visitor(Print(), v); // compile-time checking
// that all types are handled
}
The boost library has probably what you're looking for (boost::any). You can roll your own using a wrapped pointer approach if you cannot use boost...
The problem with containers like this is that when you want to access something in the container, you have to determine its type and then cast it to the actual type somehow. This is ugly, inefficient and error-prone, which is why the #1 choice in C++ is to use inheritance, unless you have a very good reason not to - something I've never actually come across in my C++ career.
I was thinking that you could just have a Pair(type, void*) and write your own pop function that casts the void* depending upon the type describe in the pair and then shove these into whatever container catches your eye.