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

class square
{
public:
	string name() const { return "square"; };
};

class circle
{
public:
	string name() const { return "circle"; };
};

class triangle
{
public:
	string name() const { return "triangle"; };
};

in a std::vector<>.

First try

So I started to investigate std::vector<std::variant>. This does seem to provide what I required.

// Variant implementation
using shapeType = variant<square, circle, triangle>;
using shapesVec = vector<shapeType>;

But if you want to do sorting you need to do something like.
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_);
};

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

class object_t
{
public:
	struct concept_t
	{
		virtual ~concept_t() = default;
		virtual unique_ptr<concept_t> copy() const = 0;
		virtual string name() const = 0;
	};

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

	unique_ptr<concept_t> _self;

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

	template<typename T>
	object_t(const T& x_) { _self = make_unique<model<T>>(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<object_t>;

auto sortByName2 = [](const object_t& l_, const object_t& r_)
{
	return l_.name() < r_.name();
};

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

//============================================================================
// Name        : poly.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <vector>
#include <string>
#include <variant>
#include <algorithm>
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<square, circle, triangle>;

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<shapeType>;

// type hiding implementation
class object_t
{
public:
	struct concept_t
	{
		virtual ~concept_t() = default;
		virtual unique_ptr<concept_t> copy() const = 0;
		virtual string name() const = 0;
	};

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

	unique_ptr<concept_t> _self;

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

	template<typename T>
	object_t(const T& x_) { _self = make_unique<model<T>>(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<object_t>;

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;
}

Leave a Reply

Your email address will not be published. Required fields are marked *

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