(C++17) Variant usage compared with union

Article directory

  • Preface and requirements
  • union
    • memory map
    • C++11 union
  • use
    • ref example
    • structure
      • Ordinary structure
      • Blanking
      • emplace
      • monostate
    • access
      • std::get<>
      • std::holds_alternative<>
      • Get pointer std::get_if<>
      • Get the optional number std::variant_size
  • END

Foreword and requirements

Union is a concept that has existed since the C language era. Mainly used in some scenarios with large memory limitations.

However, traditional C has too many restrictions, but C++ provides a safer type std::variant

union

Memory map

Let’s first look at a C language data structure memory mapping diagram.

! Internet picture (not original), invaded and deleted!

All fields in a union share a common memory area.

C++11 union

In old-time unions members cannot be of a special type. In C++11, the restriction has been lifted, as long as it is a non-application type.

In fact, union can also be discussed in a separate article. Here is a simple example without going into too much explanation:

#include <iostream>
#include <string>
#include <vector>

union U {<!-- -->
    U() {<!-- -->
    }
    ~U() {<!-- -->
    }

    static int s;

    int x;
    float y;

    std::string str;
    std::vector<int> vec;
};
// Static members of the same class are similar
int U::s = 10;

int main() {<!-- -->
    std::cout << U::s << std::endl;

    U u;

    // Use placement new and manual destruction
    new ( & amp;u.str) std::string("hello world");
    std::cout << u.str << std::endl;
    u.str.~basic_string();

    new ( & amp;u.vec) std::vector<int>(2, -1);
    u.vec.push_back(1);
    for (int i = 0; i < u.vec.size(); i + = 1) {<!-- -->
        std::cout << u.vec[i] << std::endl;
    }
    u.vec.~vector();

    return 0;
}

Use

std::variant – cppreference.com

ref example

std::variant is type-safe and can only access one type

#include <cassert>
#include <string>
#include <variant>

int main() {<!-- -->
    std::variant<int, float> v, w;
    v = 12; // v contains int
    int i = std::get<int>(v);
    w = std::get<int>(v);
    w = std::get<0>(v); // Same effect as the previous line
    w = v; // Same effect as previous line

    // std::get<double>(v); // Error: no double in [int, float]
    // std::get<3>(v); // Error: legal subscript values are 0 and 1

    try {<!-- -->
        std::get<float>(w); // w contains int instead of float: will throw
    } catch (const std::bad_variant_access & amp;) {<!-- -->
    }

    using namespace std::literals;

    std::variant<std::string> x("abc"); // The conversion constructor works when there is no ambiguity
    x = "def"; // Conversion assignment also works when there is no ambiguity

    std::variant<std::string, void const*> y("abc");
    //Convert to void const * when passing char const *
    assert(std::holds_alternative<void const*>(y)); // Success
    y = "xyz"s;
    assert(std::holds_alternative<std::string>(y)); // Success
}

Construction

Just assign the value directly.

Common structure

#include <iostream>
#include <string>
#include <variant>

void fun() {<!-- -->
    // can be the same
    std::variant<int, int> var;
    // The operation is special
}

int main() {<!-- -->
    std::variant<std::string, int> empty;
    
    std::variant<std::string, int> var("abc");
    std::cout << std::get<0>(var) << std::endl;

    var = 123;
    std::cout << std::get<1>(var) << std::endl;
    std::get<1>(var) = 654321;
    std::cout << std::get<1>(var) << std::endl;
}

Leave blank

#include <iostream>
#include <string>
#include <variant>

int main() {<!-- -->
    std::variant<int, std::string> v = "abc";
    // v.index = 1
    std::cout << "v.index = " << v.index() << '\\
';

    // Empty
    v = {<!-- -->};
    // v.index = 0
    std::cout << "v.index = " << v.index() << '\\
';
}

emplace

Can be constructed in situ

#include <iostream>
#include <string>
#include <variant>

int main() {<!-- -->
    std::variant<std::string> var;
    var.emplace<0>(2, 'a');
    std::cout << std::get<0>(var) << std::endl;

    var.emplace<std::string>("Hello World");
    std::cout << std::get<std::string>(var) << std::endl;
}

monostate

There is a kind of special situation.

Intentionally a unit type used for well-behaved std::variant hollow optionals. Specifically, a non-defaultable variant can list std::monostate as its first optional option: this makes the variant itself defaultable structure.

  • std::monostate
  • valueless_by_exception()
#include <iostream>
#include <variant>

struct S {<!-- -->
    int value;
    S(int i) : value(i) {<!-- -->
        std::cout << __func__ << std::endl;
    }
};

int main() {<!-- -->
    // This declaration will fail if there is no monostate type.
    // This is because S is not default constructible.

    std::variant<std::monostate, S> var;
    //Do not retain value
    std::cout << std::boolalpha << var.valueless_by_exception() << std::endl;

    // std::get<S> will throw an exception! We need to assign a value
    // var.index() is now 0 - first element
    std::cout << "cur index = " << var.index() << '\\
';
    var = 12;
    std::cout << std::get<S>(var).value << '\\
';
}

Access

std::get<>

Please pay attention to similar situations

#include <iostream>
#include <string>
#include <variant>

void fun0() {<!-- -->
    // can be the same
    std::variant<int, int> var;

    // Compilation fails, non-dynamic error
    // var = 10;

    // Compilation fails, non-dynamic error
    // std::get<int>(var) = 100;

    // Subscript access is possible
    std::get<0>(var) = 10;

    try {<!-- -->
        std::cout << std::get<1>(var) << std::endl;
    } catch (const std::bad_variant_access & amp; e) {<!-- -->
        // std::get: wrong index for variant
        std::cout << e.what() << std::endl;
    }
}

void fun1() {<!-- -->
    std::variant<std::string, int> var("abc");

    std::cout << std::get<0>(var) << std::endl;
    std::cout << std::get<std::string>(var) << std::endl;
}

int main() {<!-- -->
    fun0();
    fun1();
}

std::holds_alternative<>

Determine whether it can be converted

#include <iostream>
#include <string>
#include <variant>

int main() {<!-- -->
    std::cout << std::boolalpha;

    std::variant<std::string, int> var("abc");

    std::cout << "int " << std::holds_alternative<int>(var) << std::endl;
    std::cout << "string " << std::holds_alternative<std::string>(var)
              << std::endl;
}

Get pointer std::get_if<>

#include <iostream>
#include <variant>

int main() {<!-- -->
    std::variant<int, float> var;
    std::cout << & amp;var << std::endl;

    // shaping
    var = 12;
    auto pval = std::get_if<int>( & amp;var);
    std::cout << pval << std::endl;
    if (pval) {<!-- -->
        std::cout << "variant value: " << *pval << '\\
';
    } else {<!-- -->
        std::cout << "failed to get value!" << '\\
';
    }

    //floating point number
    var = 12.3f;
    auto pval2 = std::get_if<float>( & amp;var);
    std::cout << pval2 << std::endl;
    if (pval2) {<!-- -->
        std::cout << "variant value: " << *pval2 << '\\
';
    } else {<!-- -->
        std::cout << "failed to get value!" << '\\
';
    }
}
0xc10cbffba0
0xc10cbffba0
variant value: 12
0xc10cbffba0
variant value: 12.3

Get the optional number std::variant_size

determined in the compiler

  • std::variant_size
  • std::variant_size_v
#include <any>
#include <cstdio>
#include <variant>

// all pass

static_assert(std::variant_size_v<std::variant<>> == 0);
static_assert(std::variant_size_v<std::variant<int>> == 1);
static_assert(std::variant_size_v<std::variant<int, int>> == 2);
static_assert(std::variant_size_v<std::variant<int, int, int>> == 3);
static_assert(std::variant_size_v<std::variant<int, float, double>> == 3);
// std::monostate is also a placeholder
static_assert(std::variant_size_v<std::variant<std::monostate, void>> == 2);
static_assert(std::variant_size_v<std::variant<const int, const float>> == 2);
static_assert(std::variant_size_v<std::variant<std::variant<std::any>>> == 1);

int main() {<!-- -->
    std::puts("All static assertions passed.");
}

END