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>, whereIandTare the index and type to look up respectively.get_uncheckeddoes NOT check if thevariantactually holds the specifiedIorT, so only use this if you know for certain what the variant holds. This function isnoexceptand, because of the lack of checking, is faster thangetandget_if. -
visit: This visit function has abooltemplate 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 inVariantsisvalueless_by_exception, and if any are, will throw an exception. One can set theboolto 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_uncheckedto 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++17flag - x86-64 gcc 8.3 and above, with
-std=c++17flag - x64 msvc 19.24 and above, with
/std:c++17flag
C++20 on godbolt:
- x86-64 clang 10.0.0 and above, with
-std=c++20flag - x86-64 gcc 10.2 and above, with
-std=c++20flag - x64 msvc 19.28 (VS 16.9) and above, with
/std:c++17flag
Any compiler not listed here might be supported, but has not been tested.