Preamble
I was drawn to making a vector of heterogeneous types. The simplest way is to use a class derived from a base class and store points to the base class in the vector. This is simple but does not scale well as unrelated types must now have a common base class.
So as an example I wished to try to store
[js]
class square
{
public:
string name() const { return “square”; };
};
class circle
{
public:
string name() const { return “circle”; };
};
class triangle
{
public:
string name() const { return “triangle”; };
};
[/js]
in a std::vector<>.
First try
So I started to investigate std::vector<std::variant>. This does seem to provide what I required.
[js]
// Variant implementation
using shapeType = variant
using shapesVec = vector
[/js]
But if you want to do sorting you need to do something like.
[js]
auto name = [](auto&& l_)-> string { return (visit([](auto&& s_) -> string { return s_.name(); }, l_)); };
auto sortByName = [](const shapeType& l_, const shapeType& r_)
{
return name(l_) < name(r_);
};
[/js]
utilizing std::visit. In my opinion the std::visit seems to do the right thing but as we have to call down into a lambda, it does add a layer of indirection. You could do this with constexpr get<> directly in the sortByName expression, but does seem a lot of trouble.
Second try
Going back basics you could stay make a generic object_t that hides they type and store that in the std::vector. I kind of like this a bit more, but it is a bit more wordy and complicated, but more open to extension. i.e. by changing the unique_ptr to a shared_ptr allows the class to take objects of objects without copying.
See Sean Parent, Better Code: Runtime Polymorphism: https://www.youtube.com/watch?v=QGcVXgEVMJg
[js]
class object_t
{
public:
struct concept_t
{
virtual ~concept_t() = default;
virtual unique_ptr
virtual string name() const = 0;
};
template
struct model final : concept_t
{
model(T x_) : _data(move(x_)) {}
virtual unique_ptr
{
return make_unique
}
virtual string name() const override
{
return _data.name();
}
T _data;
};
unique_ptr
string name() const
{
return _self->name();
}
template
object_t(const T& x_) { _self = make_unique
object_t(const object_t& x_) { _self = x_._self->copy(); }
object_t(object_t&& x_) = default;
object_t& operator=(object_t&& x_) = default;
};
using objectVec = vector
auto sortByName2 = [](const object_t& l_, const object_t& r_)
{
return l_.name() < r_.name();
};
[/js]
Summary
std::variant seems to provide a simple way of doing a heterogeneous vector, but I still think there are better ways. std::variant has to have padding, but it doesn’t do any new’s, and copying of vector will be always by value type.
Using type hiding in a wrapper object gives the same as std::variant but although more wordy is open to extension.
So my feeling is that you still can’t really use std::variant to make a heterogeneous collection in a reasonable way.
Code in full
[js]
//============================================================================
// Name : poly.cpp
// Author :
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================
#include
#include
#include
#include
#include
using namespace std;
class square
{
public:
string name() const { return “square”; };
};
class circle
{
public:
string name() const { return “circle”; };
};
class triangle
{
public:
string name() const { return “triangle”; };
};
// Variant implementation
using shapeType = variant
auto name = [](auto&& l_)-> string { return (visit([](auto&& s_) -> string { return s_.name(); }, l_)); };
auto sortByName = [](const shapeType& l_, const shapeType& r_)
{
return name(l_) < name(r_);
};
using shapesVec = vector
// type hiding implementation
class object_t
{
public:
struct concept_t
{
virtual ~concept_t() = default;
virtual unique_ptr
virtual string name() const = 0;
};
template
struct model final : concept_t
{
model(T x_) : _data(move(x_)) {}
virtual unique_ptr
{
return make_unique
}
virtual string name() const override
{
return _data.name();
}
T _data;
};
unique_ptr
string name() const
{
return _self->name();
}
template
object_t(const T& x_) { _self = make_unique
object_t(const object_t& x_) { _self = x_._self->copy(); }
object_t(object_t&& x_) = default;
object_t& operator=(object_t&& x_) = default;
};
using objectVec = vector
auto sortByName2 = [](const object_t& l_, const object_t& r_)
{
return l_.name() < r_.name();
};
int main() {
shapesVec data;
data.emplace_back(square());
data.emplace_back(circle());
data.emplace_back(triangle());
for(const auto& v: data)
{
visit([](auto&& s_) { cout << s_.name() << " "; }, v);
}
cout << endl;
sort(data.begin(), data.end(), sortByName);
for(const auto& v: data)
{
visit([](auto&& s_) { cout << s_.name() << " "; }, v);
}
shapesVec dataa(data);
cout << endl;
for(const auto& v: dataa)
{
visit([](auto&& s_) { cout << s_.name() << " "; }, v);
}
cout << endl;
objectVec data2;
data2.emplace_back(square());
data2.emplace_back(circle());
data2.emplace_back(triangle());
for(const auto& v: data2)
{
cout << v.name() << " ";
}
cout << endl;
sort(data2.begin(), data2.end(), sortByName2);
for(const auto& v: data2)
{
cout << v.name() << " ";
}
objectVec data3(data2);
cout << endl;
for(const auto& v: data3)
{
cout << v.name() << " ";
}
return 0;
}
[/js]