All polymorphism isn’t made equal.

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 copy() const = 0;
virtual string name() const = 0;
};

template
struct model final : concept_t
{
model(T x_) : _data(move(x_)) {}
virtual unique_ptr copy() const override
{
return make_unique(*this);
}
virtual string name() const override
{
return _data.name();
}
T _data;
};

unique_ptr _self;

string name() const
{
return _self->name();
}

template
object_t(const T& x_) { _self = make_unique>(move(x_)); }

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 copy() const = 0;
virtual string name() const = 0;
};

template
struct model final : concept_t
{
model(T x_) : _data(move(x_)) {}
virtual unique_ptr copy() const override
{
return make_unique(*this);
}
virtual string name() const override
{
return _data.name();
}
T _data;
};

unique_ptr _self;

string name() const
{
return _self->name();
}

template
object_t(const T& x_) { _self = make_unique>(move(x_)); }

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]

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.