New-Style Union: std::variant
¶
Problems With C Unions, And C++ Types¶
union
with C++ can be gotten rightIt’s just unlikely
Need to call destructors explicitly
Other weirdnesses
⟶ no!
⟶
std::variant
Instantiation, Default Initialization, Member Access¶
#include <gtest/gtest.h>
#include <variant>
TEST(variant_suite, default_ctor)
{
std::variant<int, float> v; // <--- default: int{}
ASSERT_EQ(std::get<int>(v), 0); // <--- access: by-type
ASSERT_EQ(std::get<0>(v), 0); // <--- access: by-position
ASSERT_EQ(v.index(), 0); // <--- which position does it hold?
ASSERT_TRUE(std::holds_alternative<int>(v)); // <--- which type does it hold?
v = 36.5f; // <--- now has float
// ...
ASSERT_FLOAT_EQ(std::get<float>(v), 36.5);
ASSERT_FLOAT_EQ(std::get<1>(v), 36.5);
ASSERT_EQ(v.index(), 1);
ASSERT_TRUE(std::holds_alternative<float>(v));
}
Non-Default Initialization¶
#include <gtest/gtest.h>
#include <variant>
TEST(variant_suite, converting_ctor)
{
std::variant<int, float> v{36.5f}; // <--- holds float
ASSERT_FLOAT_EQ(std::get<float>(v), 36.5);
ASSERT_FLOAT_EQ(std::get<1>(v), 36.5);
ASSERT_EQ(v.index(), 1);
}
Accessing Non-Current Member: std::bad_variant_access
¶
#include <gtest/gtest.h>
#include <variant>
TEST(variant_suite, bad_access)
{
std::variant<int, float> v{42};
try {
std::get<float>(v);
}
catch (const std::bad_variant_access&) {
SUCCEED();
}
}
The Visitor¶
#include <gtest/gtest.h>
#include <variant>
struct Visitor
{
Visitor() : int_seen{false}, float_seen{false} {}
void operator()(int) { int_seen = true; }
void operator()(float) { float_seen = true; }
bool int_seen, float_seen;
};
TEST(variant_suite, visit)
{
std::variant<int, float> v;
Visitor vis;
std::visit(vis, v);
ASSERT_TRUE(vis.int_seen);
v = 36.5f;
}
Non-Throwing Accessor: std::get_if<>
¶
Exceptions are bad (at least in Embedded)
⟶ back to pointers 🤘
#include <gtest/gtest.h>
#include <variant>
TEST(variant_suite, get_if)
{
std::variant<int, float> v{42};
const int* pi = std::get_if<int>(& /*REALLY!*/ v);
ASSERT_NE(pi, nullptr);
ASSERT_EQ(*pi, 42);
const float* pf = std::get_if<float>(&v);
ASSERT_EQ(pf, nullptr);
}
std::string
And const char*
Are Different¶
#include <gtest/gtest.h>
#include <string>
#include <variant>
TEST(variant_suite, string_charp)
{
std::variant<std::string, const char*> v;
ASSERT_EQ(std::get<std::string>(v), std::string());
const char* s = "blah";
v = s;
ASSERT_EQ(std::get<const char*>(v), s);
v = "blah";
ASSERT_EQ(std::get_if<std::string>(&v), nullptr);
}
And User Defined Types?¶
#include <gtest/gtest.h>
#include <variant>
struct Foo
{
Foo() = delete;
Foo(void*) {}
};
TEST(variant_suite, no_default_ctor)
{
// std::variant<Foo> v; // <--- error: no default ctor
std::variant<Foo> v{nullptr}; // <--- OK
(void)v;
}
And Memory Usage?¶
Not defined by standard
Results presented are from GCC
$ gcc --version gcc (GCC) 13.3.1 20240913 (Red Hat 13.3.1-3) Copyright (C) 2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
What does make sense?
⟶ A typesafe union has to carry a type discriminator: at least as wide as a
uint8_t
for < 256 different types
#include <variant>
#include <cstdint>
#include <iostream>
int main()
{
{
std::variant<uint8_t, uint8_t> v;
std::cout << sizeof(v) << std::endl;
}
return 0;
}
$ ./c++11-variant-mem-usage-2-uint8
2