A realtime/embedded-friendly C++11 variant type which is never empty and prevents undesirable implicit conversions

Overview

strict variant

Build Status Appveyor status Boost licensed

Do you use boost::variant or one of the many open-source C++11 implementations of a "tagged union" or variant type in your C++ projects?

boost::variant is a great library. I created strict_variant in order to address a few things about boost::variant that I didn't like.

The tl;dr version is that unlike boost::variant or std::variant, strict_variant will never throw an exception or make a dynamic allocation in the effort of supporting types that have throwing moves. The default version will simply fail a static assert if this would happen. The strict_variant::easy_variant will make allocations in this situation, so you can opt-in to that if you want, and these two versions of variant "play nicely" together. This kind of thing is often a major concern in projects with realtime requirements, or in embedded devices, which may not allow, or simply may not have these C++ features. If you are making a library that might be used in "conventional" projects that want the ease-of-use that comes from boost::variant, but might also be used in projects with restrictive requirements, and you want to use a variant type as part of the API, strict_variant might offer a way to keep everyone happy.

Besides this, there are some issues in the interface of variant that were addressed that make it more pleasant to use day-to-day IMHO. (These were actually the original motivation of the project.)

  • I didn't like that code like this may compile without any warning or error messages:

    boost::variant<std::string, int> v;  
    
    v = true;  

    I'd usually rather that my variant is more restrictive about what implicit conversions can happen.

  • I wanted that things like this should compile and do what makes sense, even if overload resolution would be ambiguous.

    variant<bool, long, double, std::string> v;  
    
    v = true;  // selects bool
    v = 10;    // selects long 
    v = 20.5f; // selects double
    v = "foo"; // selects string

    I also wanted that such behavior (what gets selected in such cases) is portable.

    (For code examples like this, where boost::variant has unfortunate behavior, see "Abstract and Motivation" in the documentation.)

    In strict_variant we modify overload resolution in these situations by removing some candidates.

    For instance:

    • We eliminate many classes of problematic conversions, including narrowing and pointer conversions.
    • We prohibit standard conversions between bool, integral, floating point, pointer, character, and some other classes.
    • We impose "rank-based priority". If two integer promotions are permitted but one of them is larger than another, the larger one gets discarded,
      e.g. if int -> long and int -> long long are candidates, the long long is eliminated.

    See documentation for details.

  • I didn't like that boost::variant will silently make backup copies of my objects. For instance, consider this simple program, in which A and B have been defined to log all ctor and dtor calls.

    int main() {
      using var_t = boost::variant<A, B>;
    
      var_t v{A()};
      std::cout << "1" << std::endl;
      v = B();
      std::cout << "2" << std::endl;
      v = A();
      std::cout << "3" << std::endl;
    }

    The boost::variant produces the following output:

    A()
    A(A&&)
    ~A()
    1
    B()
    B(B&&)
    A(const A &)
    ~A()
    B(const B &)
    ~A()
    ~B()
    ~B()
    2
    A()
    A(A&&)
    B(const B &)
    ~B()
    A(const A &)
    ~B()
    ~A()
    ~A()
    3
    ~A()

    This may be pretty surprising to some programmers.

    By contrast, if you use the C++17 std::variant, or one of the variants with "sometimes-empty" semantics, you get something like this (this output from std::experimental::variant)

    A()
    A(A&&)
    ~A()
    1
    B()
    ~A()
    B(B&&)
    ~B()
    2
    A()
    ~B()
    A(A&&)
    ~A()
    3
    ~A()

    This is much closer to what the naive programmer expects who doesn't know about internal details of boost::variant -- the only copies of his objects that exist are what he can see in his source code.

    This kind of thing usually doesn't matter, but sometimes if for instance you are debugging a nasty memory corruption problem (perhaps there's bad code in one of the objects contained in the variant), then these extra objects, moves, and copies, may make things incidentally more complicated.

    Here's what you get with strict_variant:

    A()
    A(A&&)
    ~A()
    1
    B()
    B(B&&)
    ~A()
    ~B()
    2
    A()
    A(A&&)
    ~B()
    ~A()
    3
    ~A()

    Yet, strict_variant does not have an empty state, and is fully exception-safe!

    (These examples from gcc 5.4, see code in example folder.)

    To summarize the differences:

    • std::variant is rarely-empty, always stack-based. In fact, it's empty exactly when an exception is thrown. Later, it throws different exceptions if you try to visit when it is empty.
    • boost::variant is never-empty, usually stack-based. It has to make a dynamic allocation and a backup copy whenever an exception could be thrown, but that gets freed right after if an exception is not actually thrown.
    • strict_variant is never-empty, and stack-based exactly when the current value-type is nothrow moveable. It never makes a backup move or copy, and never throws an exception.

    Each approach has its merits. I chose the strict_variant approach because I find it simpler and it avoids what I consider to be drawbacks of boost::variant and std::variant. And, if you manage to make all your types no-throw move constructible, which I often find I can, then strict_variant gives you optimal performance, the same as std::variant, without an empty state.

For an in-depth discussion of the design, check out the documentation.

For a gentle intro to variants, and an overview of strict-variant, see slides from a talk I gave about this: [pptx][pdf]

Documentation

On github pages.

Compiler Compatibility

strict_variant targets the C++11 standard.

It is known to work with gcc >= 4.8 and clang >= 3.5, and is tested against MSVC 2015.

strict_variant can be used as-is in projects which require -fno-exceptions and -fno-rtti.

Licensing and Distribution

strict variant is available under the boost software license.

You might also like...
A library of type safe sets over fixed size collections of types or values, including methods for accessing, modifying, visiting and iterating over those.

cpp_enum_set A library of type safe sets over fixed size collections of types or values, including methods for accessing, modifying, visiting and iter

ring-span lite - A C++yy-like ring_span type for C++98, C++11 and later in a single-file header-only library

ring-span lite: A circular buffer view for C++98 and later Contents Example usage In a nutshell Dependencies Installation Synopsis Reported to work wi

Fixed point 48.16 bit arithmetic type

fixed_math written from scratch fixed point math library in C++17 features minimum c++17 compiler required fixed point 48.16 arithmethic strong type w

Functional, Type safe, Lazy abstractions for generic iterators in C

C-Iterplus Functional abstractions for working with iterators, as demonstrated in c-iterators. Finding the longest common prefix of an array of string

C++ Type Traits for Smart Pointer
C++ Type Traits for Smart Pointer

SmartPointerTypeTrait C++ Type Traits for Smart Pointer is_a_pointer is_smart_pointer template typename T struct is_smart_ptr : is_smart_ptr_impl

Algo-Tree is a collection of Algorithms and data structures which are fundamentals to efficient code and good software design. Creating and designing excellent algorithms is required for being an exemplary programmer. It contains solutions in various languages such as C++, Python and Java. Generic single-file implementations of AVL tree in C and C++ suitable for deeply embedded systems
Generic single-file implementations of AVL tree in C and C++ suitable for deeply embedded systems

Cavl Generic single-file implementation of AVL tree suitable for deeply embedded systems. Simply copy cavl.h or cavl.hpp (depending on which language

Algo-Tree is a collection of Algorithms and data structures which are fundamentals to efficient code and good software design
Algo-Tree is a collection of Algorithms and data structures which are fundamentals to efficient code and good software design

Algo-Tree is a collection of Algorithms and data structures which are fundamentals to efficient code and good software design. Creating and designing excellent algorithms is required for being an exemplary programmer. It contains solutions in various languages such as C++, Python and Java.

This repository aims to contain solutions and explanations to various competitive programming problems, which may be important for interviews and online tests of different companies.
This repository aims to contain solutions and explanations to various competitive programming problems, which may be important for interviews and online tests of different companies.

Competitive Programming Solutions Compilation Hello everyone 👋 This repository contains solutions and explanations to various competitive programming

Comments
  • ambiguity issue with GCC

    ambiguity issue with GCC

    Thank you for providing your implementation of variant. I'm just playing around with it and stumbled over an issue. The following code builds with Clang but fails with GCC (5, 6, 7) due to an ambiguity of initializer<T>::operator():

    struct S {
        explicit S (const std::string &str) {}
    };
    
    strict_variant::variant<std::string, S> var{std::string("string")};
    
    variant.hpp:241:37: error: request for member ‘operator()’ is ambiguous
         return decltype(initializer<T>{}(std::declval<T>()))::value;
    

    Is this intentional behavior or a bug? The code builds correctly with boost::variant.

    opened by mgieseki 4
  • C4348 redefinition of default parameter (variant.hpp:183)

    C4348 redefinition of default parameter (variant.hpp:183)

    Hi! I'm getting this warning with MSVC 2015 (19.00.23918)

    craftr.lib.strict-variant-1.0.0\data/strict-variant-0.3\include\strict_variant/variant.hpp(183): warning C4348: 'strict_variant::variant<int,std::string>::initializer': redefinition of default parameter: parameter 2
    craftr.lib.strict-variant-1.0.0\data/strict-variant-0.3\include\strict_variant/variant.hpp(188): note: see declaration of 'strict_variant::variant<int,std::string>::initializer'
    C:\Users\niklas\repos\craftr\craftr\examples\examples.cpp\main.cpp(113): note: see reference to class template instantiation 'strict_variant::variant<int,std::string>' being compiled
    

    I'm not sure how to fix it, otherwise I'd make a PR 😊

    opened by NiklasRosenstein 4
  • Single-header version

    Single-header version

    Hi, thanks for this great project! I've been looking for something like this 🙂 std::variant IMO violates C++'s "don't pay for what you don't use" principle (e.g. lots of valueless_by_exception checks) and doesn't fit my projects.

    Would it be possible to create a single-header version? I can concatenate the files myself, but it would be nice to have it automatically generated when there's a new release.

    [EDIT] Just wanted to add that I get about 35% better overall performance in my benchmarks, compared to mpark::variant. This is great 👍

    opened by martinfinke 2
  • Combine the lambda of visitor

    Combine the lambda of visitor

    Hi,

    Would you mine adding a make_visitor as seen on this page to allow using lambada with multiple type handling ? Ref: https://bitbashing.io/std-visit.html

    Ex :

    [](auto& arg) {
        using T = std::decay_t<decltype(arg)>;
    
        if constexpr (std::is_same_v<T, string>) {
            printf("string: %s\n", arg.c_str());
            // ...
        }
        else if constexpr (std::is_same_v<T, int>) {
            printf("integer: %d\n", arg);
            // ...
        }
        else if constexpr (std::is_same_v<T, bool>) {
            printf("bool: %d\n", arg);
            // ...
        }
    }
    
    opened by erakis 1
Releases(v0.5)
  • v0.5(Aug 7, 2018)

  • v0.4(Dec 18, 2016)

    • Add operator = (T &&) ctor and friends (I finally became convinced of the wisdom of this)
    • Refactor type-changing assignment code and visitors to be better organized
    • Refactor initialzation to avoid extra move / copy, fix warnings on msvc
    • Almost total rewrite of "overview" part of documentation
    Source code(tar.gz)
    Source code(zip)
  • v0.3(Aug 16, 2016)

    • Implement swappable for variant
    • Refactor operator == so that the implementation is symmetric
    • Refactor safely_constructible trait, and document the numerous customization points that now exist
    • Add emplace-by-index method, fix corner case behavior of emplace
    • Consolidate private visitors of variant class template
    • Refactor implementation of apply_visitor, make it noexcept correct, also add C++17 visit method syntax
    • Remove some cruft from recursive_wrapper
    Source code(tar.gz)
    Source code(zip)
  • v0.2(Aug 13, 2016)

    • Modified the forwarding-reference constructor, to make it better behaved and easier to customize dominates type trait, in addition to safely_constructible, are now used, and e.g. long long is not considered necessarily a worse match for int than unsigned long.
    • Added much documentation and tests
    • Still not v1.0
    Source code(tar.gz)
    Source code(zip)
  • v0.1(Aug 6, 2016)

    This is a beta-quality release. It is essentially a refactor of a much smaller library (see initial commit). That library was stable, having been factored out from another large project.

    However, since then a good deal of the code has been rewritten to provide a much more powerful and useful container. This development cycle has seen the library take on a more full form.

    Plan is to tick to 1.0 after testing over an extended period of time within the larger project, and pending user feedback / bug reports.

    Source code(tar.gz)
    Source code(zip)
Owner
Chris Beck
Art (in picture) by: https://kevintitzer.com/Sludge-From-NY-NJ
Chris Beck
Eggs.Variant is a C++11/14/17 generic, type-safe, discriminated union.

Eggs.Variant Introduction Eggs.Variant is a C++11/14/17 generic, type-safe, discriminated union. See the documentation at http://eggs-cpp.github.io/va

null 138 Dec 3, 2022
COBS framing with implicit run-length-encoding, optimized for data containing statistically a bit more 0 and FF bytes in a row, as data often carry 16, 32 or 64 bit numbers with small values.

TCOBS Table of Contents About The project TCOBS Specification TCOBS code Getting Started 4.1. Prerequisites 4.2. Installation 4.3. Usage in Go 4.3.1.

Thomas Höhenleitner 17 Nov 6, 2022
Get the ability to use variable argument lists in C++ without requiring the first parameter! Meant to support a WG14 proposal to fix up not having empty argument lists.

Vargs Alright, it's time to commit code crimes for the greater good! What if you did not need to pass an initial parameter to your C++ ... functions?

Shepherd's Oasis 6 Dec 23, 2022
Cross-platform STL-styled and STL-compatible library with implementing containers, ranges, iterators, type traits and other tools; actors system; type-safe config interface.

Yato A small repository where I'm gatherting useful snippets and abstractions for C++ development. Yato includes 3 main modules: multidimensional cont

Alexey 10 Dec 18, 2022
Strong typedef - A class template that creates a new type that is distinct from the underlying type, but convertible to and from it

jss::strong_typedef <Tag, ValueType, Properties...> This is an implementation of a C++17 class template that provides a wrapper type that is convertib

Anthony Williams 101 Dec 21, 2022
Finite State Machine implementation using std::variant

mp::fsm Implementation of Finite State Machine presented by me on CppCon 2018 in a talk Effective replacement of dynamic polymorphism with std::varian

Mateusz Pusz 65 Jan 3, 2023
A family of header-only, very fast and memory-friendly hashmap and btree containers.

The Parallel Hashmap Overview This repository aims to provide a set of excellent hash map implementations, as well as a btree alternative to std::map

Gregory Popovitch 1.7k Jan 9, 2023
This is a beginner-friendly project aiming to build a problem-set on different data structures and algorithms in different programming languages.

DSAready Overview This is a beginner-friendly project that aims to create a problem-set for various Data Structures and Algorithms. Being a programmer

Riddhi Jain 13 Aug 17, 2022
Easy to use, header only, macro generated, generic and type-safe Data Structures in C

C Macro Collections Easy to use, header only, macro generated, generic and type-safe Data Structures in C. Table of Contents Installation Contributing

Leonardo Vencovsky 347 Jan 8, 2023
Library of generic and type safe containers in pure C language (C99 or C11) for a wide collection of container (comparable to the C++ STL).

M*LIB: Generic type-safe Container Library for C language Overview M*LIB (M star lib) is a C library enabling to use generic and type safe container i

PpHd 571 Jan 5, 2023