This is a single-header C++17 implementation of variant, also known as a sum type, type-safe union, and discriminated union, where the types are stored in a binary tree fashion inside a union. The space required for a variant is the variant's largest contained type plus the size of an integral type, which serves as the index and keeps track of what the variant contains. For example, in a variant with 5 types A, B, C, D, E, type C will be the root node.
Index | Class |
---|---|
0 | A |
1 | B |
2 | C |
3 | D |
4 | E |
Graphic representiation of how the elements are stored in a union:
---Union--- <-This, instead of this-> ---Union---
/ \ / \
C (root node) E
/ \ \
A D D
\ \ \
B E C
\
B
\
A
While most other implementations have O(N) depth, this version, which you may have guessed after viewing the binary tree, only has O(log N) template instantiation depth. Accessing elements, like the get
, get_if
, and the get_unchecked
method shall also have O(log N) depth.
The code emulates much of the standard implementation's behavior, described at cppreference. Differences are described below.
-
get_unchecked
: To use this variant’s get function, useszLogVar::get<I>(yourVariant)
orszLogVar::get<T>(yourVariant)
, similar to std’s versionstd::get<I>
andstd::get<T>
. Thus, to use the get_unchecked version, useszLogVar::get_unchecked<I>
orszLogVar::get_unchecked<T>
, whereI
andT
are the index and type to look up respectively.get_unchecked
does NOT check if thevariant
actually holds the specifiedI
orT
, so only use this if you know for certain what the variant holds. This function isnoexcept
and, because of the lack of checking, is faster thanget
andget_if
. -
visit
: This visit function has abool
template parameter calledcheckIfValueless
. By default, it is true, so one can use it with the familiar syntaxszLogVar::visit( Visitor, Variants…)
. When it’s true, it checks if any of the variants inVariants
isvalueless_by_exception
, and if any are, will throw an exception. One can set thebool
to false by calling the visit function like so:szLogVar::visit<false>( Visitor, Variants…)
. This proves useful if the user is certain none of the variants they want to visit will be valueless. It also usesget_unchecked
to retrieve the types held in the variants forVisitor
's invocation.
Note: This visit
method isn't strictly standard. When the number of variants passed into the function is 1, the complexity isn't constant time. For any nonzero number of variants, the visit
method uses a modified binary search, an algorithm that takes O(log N) time, to reach the correct index. This circumvents the limitations of function pointers that Michael Park describes here.
#include "../include/szLogVar/variant.hpp"
#include <iostream>
int main(){
szLogVar::variant<bool, std::string> myVar{"ABC"};
std::cout << szLogVar::get<1>(myVar) << '\n'; //prints "ABC"
myVar = true; //Just assigned a bool to it, so no need for checking. Use szLogVar::get_unchecked
std::cout << std::boolalpha << szLogVar::get_unchecked<bool>(myVar) << '\n'; //prints "true"
myVar = "DEF";
std::cout << szLogVar::get_unchecked<std::string>(myVar) << '\n'; //std::string again. Use get_unchecked<std::string>. prints "DEF"
}
Test name | szLogVar time (seconds) C++17 | std time (seconds) C++17 | szLogVar time (seconds) C++20 | std time (seconds) C++20 | Test description |
---|---|---|---|---|---|
timeConvertCtor | 5.174 | 29.164 | 4.813 | 29.278 | Measures variant's converting constructor. Variant has 200 types. |
timeDefaultConstruct | 3.849 | 3.647 | 4.563 | 4.217 | Measures variant's default constructor. Variant has 200 types. |
timeGetIdx | 10.379 | 43.626 | 9.93 | 45.857 | Measures szLogVar::get and std::get , with a specified index. Variant has 300 types. |
timeGetType | 15.155 | 48.082 | 16.191 | 46.931 | Measures szLogVar::get and std::get , with a specified type. Variant has 300 types. |
timeSmallVariants | 11.445 | 8.138 | 7.162 | 9.03 | Measures default construction of 1000 variants with 3 types |
timeSmallVariantsConvertCtor | 12.118 | 11.828 | 7.273 | 12.161 | Measures converting construction of 1000 variants with 3 types |
timeTestVisit | 12.136 | 21.419 | 12.697 | 24.207 | Measures visit , with 5 variants with 5 types each. |
timeVoidVisit | 10.98 | 21.513 | 11.861 | 22.575 | Measures visit with a lambda returning void, which allows for smaller template instantiation depth, with 5 variants with 5 types each. |
szLogVar::variant
displays a noticeable improvement in compile time if the variant holds many types, though its performance is very similar to std::variant
when the types are few.
Flags used were -c
, -ftime-trace
, and -std=c++17
or -std=c++20
depending on the standard tested.
Compiler: clang++ version 12.0.0
Target: x86_64-apple-darwin19.6.0
Supported compilers:
C++17 on godbolt:
- x86-64 clang 5.0.0 and above, with
-std=c++17
flag - x86-64 gcc 8.3 and above, with
-std=c++17
flag - x64 msvc 19.24 and above, with
/std:c++17
flag
C++20 on godbolt:
- x86-64 clang 10.0.0 and above, with
-std=c++20
flag - x86-64 gcc 10.2 and above, with
-std=c++20
flag - x64 msvc 19.28 (VS 16.9) and above, with
/std:c++17
flag
Any compiler not listed here might be supported, but has not been tested.