What are the advantages of using std::variant as opposed to traditional polymorphic processing?
So, is variant just a convenience?
No, they are different concepts. Main difference that on one side std::variant
can work with unrelated types including builtins like int
which is not possible with virtual functions directly. On another side std::variant
must know types it is working with at compile time. For example it is possible to add a type with virtual function(s) by just linking additional object module without recompiling rest of the code or loading a shared library dynamically to existing application (you do not even have to restart the app) while with std::variant
you must recompile code dealing with types std::variant
contains.
Enforcing a common interface with std::variant without inheritance
The simplest and quite execution time optimal solution is have separate container for each type. Any example showing that Data Oriented Design is better then Object Oriented Programing is using this approach to show difference in performance.
Other way is to create some wrapper for variant:
class VisualElement
{
BaseElement* self;
std::variant<Circle, Image, Polygon> item;
public:
template<typename T, bool = std::is_base_of_v<BaseElement, T>>
VisualElement(const &T other) {
item = other;
self = &item.get<T>();
}
template<typename T, bool = std::is_base_of_v<BaseElement, T>>
VisualElement& operator=(const &T other) {
item = other;
self = &item.get<T>();
return *this;
}
bool hitTest(Point p) {
return self->hitTest(p);
// or use of std::visit and drop common interface ancestor.
}
Rect boundingRect() {
return self->boundingRect();
}
std::string uniqueId() {
return self->uniqueId();
}
};
Techniques for cutting down on verbosity when do polymorphism via std::variant rather than inheritance
Just use a visitor:
#include <variant>
struct state_a{};
struct state_b{};
struct actor
{
std::variant<state_a, state_b> state;
};
// basically no need to declare them as members.
void rotate(state_a, double deg)
{
// do a
}
void rotate(state_b, double deg)
{
// do b
}
void rotate(actor& a, double deg)
{
std::visit([deg](auto &state){ rotate(state, deg); }, a.state);
}
Here is a more scalable version with templates:
https://godbolt.org/z/58Kjx9h5W
// actor.hpp
#include <variant>
template <typename ... StatesT>
struct actor
{
std::variant<StatesT...> state;
};
template <typename StateT>
void rotate(StateT& s, double deg)
{
return rotate(s, deg);
}
template <typename ... StatesT>
void rotate(actor<StatesT...> &a, double deg)
{
std::visit([deg](auto &state){ rotate(state, deg); }, a.state);
}
// other headers
#include <iostream>
struct state_a{};
struct state_b{};
// basically no need to declare them as members.
void rotate(state_a, double deg)
{
// do a
std::cout << "rotating a\n";
}
void rotate(state_b, double deg)
{
// do b
std::cout << "rotating a\n";
}
int main()
{
auto actr = actor<state_a, state_b>{state_a()};
rotate(actr, 30);
}
Be advised, however, that "recursive" template <typename StateT> void rotate(StateT& s, double deg)
will not work if you use a namespace for your states because of ADL.
You either declare rotate
AND state_a
in the same namespace.
std::variant vs pointer to base class for heterogeneous containers in C++
std::variant<A,B,C>
holds one of a closed set of types. You can check whether it holds a given type with std::holds_alternative
, or use std::visit
to pass a visitor object with an overloaded operator()
. There is likely no dynamic memory allocation, however, it is hard to extend: the class with the std::variant
and any visitor classes will need to know the list of possible types.
On the other hand, BaseClass*
holds an unbounded set of derived class types. You ought to be holding std::unique_ptr<BaseClass>
or std::shared_ptr<BaseClass>
to avoid the potential for memory leaks. To determine whether an instance of a specific type is stored, you must use dynamic_cast
or a virtual
function. This option requires dynamic memory allocation, but if all processing is via virtual
functions, then the code that holds the container does not need to know the full list of types that could be stored.
std::variant vs std::any when type is move constructible
If you know the types, use std::variant
. In that case you know all the possible types, and use std::variant::visit() to apply the visitor to the variants.
If you don't, use std::any
. You can think about this when dealing with types that are generic. In other words, when the types are not known apriori.
Which one will give the least overhead?
std::variant
, cannot use the heap. It's not allowed to. There is no way to actually have a structure containing a possible variant of itself without a dynamic allocation, as such a structure can easily be shown to be infinite in size if statically declared. Source.
In contrast, std::any
may do so. As a result, the first will have faster construction and copy operations.
Related Topics
Benefits and Portability of Boost Library
Comparison of Arrays in Google Test
Should I Use a C++ Reinterpret_Cast Over a C-Style Cast
Elegant Way to Implement Extensible Factories in C++
How to Pass and Execute Anonymous Function as Parameter in C++11
Defining Operator< for a Struct
Is "Long Long" = "Long Long Int" = "Long Int Long" = "Int Long Long"
To Stl or !Stl, That Is the Question
How to Read/Write Std::String Values From/To Binary Files
An Expensive Jump with Gcc 5.4.0
How to Require an Exact Function Signature in the Detection Idiom
Access to Protected Member Through Member-Pointer: Is It a Hack
Std::Stod Throws Out_Of_Range Error for a String That Should Be Valid
Opencv Edge/Border Detection Based on Color
Sizeof an Array Passed as Function Argument
What Is the Status on Dynarrays