buffet: add C++ variant type (Any).
Any is the class that adds support for native variant types.
Any can store any C++ type which is copyable. Moveable-only
types (such as std::unique_ptr) cannot be stored directly,
however you can store pointers (or references) to such types.
Any stores small data embedded into the class itself without
having to allocate a separate storage for it. Larger data
(roughly, larger than 8 bytes) or non trivially-copyable types
are stored in a separate memory block and the pointer to that
block is stored in the Any class itself.
Any supports move semantics. You can move the stored value
from one Any into another without copying it.
At this point, there is no support for comparing the Any class
(equal or less than), so you cannot use it as a key in a map,
however you still can store it as a value.
The code that extracts the data from Any must know the exact
type stored. Calling Any::Get<T>() for incompatible type is
not supported. However you can use Any::TryGet<T>() instead
and provide a default value, which will be returned if Any
does not contain the expected data.
You can also inspect the type of the contained data by
using Any::GetType() and Any::IsTypeCompatible<T>() methods.
BUG=chromium:366709
TEST=All existing and new unit tests pass.
Change-Id: Ic47529935ddd39c792dff1db8667c76db91b16d4
Reviewed-on: https://chromium-review.googlesource.com/198590
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/any_unittest.cc b/buffet/any_unittest.cc
new file mode 100644
index 0000000..48df826
--- /dev/null
+++ b/buffet/any_unittest.cc
@@ -0,0 +1,245 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "buffet/any.h"
+
+using buffet::Any;
+
+TEST(Any, Empty) {
+ Any val;
+ EXPECT_TRUE(val.IsEmpty());
+
+ Any val2 = val;
+ EXPECT_TRUE(val.IsEmpty());
+ EXPECT_TRUE(val2.IsEmpty());
+}
+
+TEST(Any, SimpleTypes) {
+ Any val(20);
+ EXPECT_FALSE(val.IsEmpty());
+ EXPECT_TRUE(val.IsTypeCompatible<int>());
+ EXPECT_EQ(20, val.Get<int>());
+
+ Any val2(3.1415926);
+ EXPECT_FALSE(val2.IsEmpty());
+ EXPECT_TRUE(val2.IsTypeCompatible<double>());
+ EXPECT_FALSE(val2.IsTypeCompatible<int>());
+ EXPECT_DOUBLE_EQ(3.1415926, val2.Get<double>());
+
+ Any val3(std::string("blah"));
+ EXPECT_TRUE(val3.IsTypeCompatible<std::string>());
+ EXPECT_EQ("blah", val3.Get<std::string>());
+}
+
+TEST(Any, Clear) {
+ Any val('x');
+ EXPECT_FALSE(val.IsEmpty());
+ EXPECT_EQ('x', val.Get<char>());
+
+ val.Clear();
+ EXPECT_TRUE(val.IsEmpty());
+}
+
+TEST(Any, Assignments) {
+ Any val(20);
+ EXPECT_EQ(20, val.Get<int>());
+
+ val = 3.1415926;
+ EXPECT_FALSE(val.IsEmpty());
+ EXPECT_TRUE(val.IsTypeCompatible<double>());
+ EXPECT_DOUBLE_EQ(3.1415926, val.Get<double>());
+
+ val = std::string("blah");
+ EXPECT_EQ("blah", val.Get<std::string>());
+
+ Any val2;
+ EXPECT_TRUE(val2.IsEmpty());
+ val2 = val;
+ EXPECT_FALSE(val.IsEmpty());
+ EXPECT_FALSE(val2.IsEmpty());
+ EXPECT_EQ("blah", val.Get<std::string>());
+ EXPECT_EQ("blah", val2.Get<std::string>());
+ val.Clear();
+ EXPECT_TRUE(val.IsEmpty());
+ EXPECT_EQ("blah", val2.Get<std::string>());
+ val2.Clear();
+ EXPECT_TRUE(val2.IsEmpty());
+
+ val = std::vector<int>({100, 20, 3});
+ auto v = val.Get<std::vector<int>>();
+ EXPECT_EQ(100, v[0]);
+ EXPECT_EQ(20, v[1]);
+ EXPECT_EQ(3, v[2]);
+}
+
+TEST(Any, Enums) {
+ enum class Dummy { foo, bar, baz };
+ Any val(Dummy::bar);
+ EXPECT_FALSE(val.IsEmpty());
+ EXPECT_TRUE(val.IsConvertibleToInteger());
+ EXPECT_EQ(Dummy::bar, val.Get<Dummy>());
+ EXPECT_EQ(1, val.GetAsInteger());
+
+ val = Dummy::baz;
+ EXPECT_EQ(2, val.GetAsInteger());
+
+ val = Dummy::foo;
+ EXPECT_EQ(0, val.GetAsInteger());
+}
+
+TEST(Any, Integers) {
+ Any val(14);
+ EXPECT_TRUE(val.IsConvertibleToInteger());
+ EXPECT_EQ(14, val.Get<int>());
+ EXPECT_EQ(14, val.GetAsInteger());
+
+ val = '\x40';
+ EXPECT_TRUE(val.IsConvertibleToInteger());
+ EXPECT_EQ(64, val.Get<char>());
+ EXPECT_EQ(64, val.GetAsInteger());
+
+ val = static_cast<uint16_t>(65535);
+ EXPECT_TRUE(val.IsConvertibleToInteger());
+ EXPECT_EQ(65535, val.Get<uint16_t>());
+ EXPECT_EQ(65535, val.GetAsInteger());
+
+ val = static_cast<uint64_t>(0xFFFFFFFFFFFFFFFFULL);
+ EXPECT_TRUE(val.IsConvertibleToInteger());
+ EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, val.Get<uint64_t>());
+ EXPECT_EQ(-1, val.GetAsInteger());
+
+ val = "abc";
+ EXPECT_FALSE(val.IsConvertibleToInteger());
+
+ int a = 5;
+ val = &a;
+ EXPECT_FALSE(val.IsConvertibleToInteger());
+}
+
+TEST(Any, Pointers) {
+ Any val("abc"); // const char*
+ EXPECT_FALSE(val.IsTypeCompatible<char*>());
+ EXPECT_TRUE(val.IsTypeCompatible<const char*>());
+ EXPECT_FALSE(val.IsTypeCompatible<volatile char*>());
+ EXPECT_TRUE(val.IsTypeCompatible<volatile const char*>());
+ EXPECT_STREQ("abc", val.Get<const char*>());
+
+ int a = 10;
+ val = &a;
+ EXPECT_TRUE(val.IsTypeCompatible<int*>());
+ EXPECT_TRUE(val.IsTypeCompatible<const int*>());
+ EXPECT_TRUE(val.IsTypeCompatible<volatile int*>());
+ EXPECT_TRUE(val.IsTypeCompatible<volatile const int*>());
+ EXPECT_EQ(10, *val.Get<const int*>());
+ *val.Get<int*>() = 3;
+ EXPECT_EQ(3, a);
+}
+
+TEST(Any, Arrays) {
+ // The following test are here to validate the array-to-pointer decay rules.
+ // Since Any does not store the contents of a C-style array, just a pointer
+ // to the data, putting array data into Any could be dangerous.
+ // Make sure the array's lifetime exceeds that of an Any containing the
+ // pointer to the array data.
+ // If you want to store the array with data, use corresponding value types
+ // such as std::vector or a struct containing C-style array as a member.
+
+ int int_array[] = {1, 2, 3}; // int*
+ Any val = int_array;
+ EXPECT_TRUE(val.IsTypeCompatible<int*>());
+ EXPECT_TRUE(val.IsTypeCompatible<const int*>());
+ EXPECT_TRUE(val.IsTypeCompatible<int[]>());
+ EXPECT_TRUE(val.IsTypeCompatible<const int[]>());
+ EXPECT_EQ(3, val.Get<int*>()[2]);
+
+ const int const_int_array[] = {10, 20, 30}; // const int*
+ val = const_int_array;
+ EXPECT_FALSE(val.IsTypeCompatible<int*>());
+ EXPECT_TRUE(val.IsTypeCompatible<const int*>());
+ EXPECT_FALSE(val.IsTypeCompatible<int[]>());
+ EXPECT_TRUE(val.IsTypeCompatible<const int[]>());
+ EXPECT_EQ(30, val.Get<const int*>()[2]);
+}
+
+TEST(Any, References) {
+ // Passing references to object via Any might be error-prone or the
+ // semantics could be unfamiliar to other developers. In many cases,
+ // using pointers instead of references are more conventional and easier
+ // to understand. Even though the cases of passing references are quite
+ // explicit on both storing and retrieving ends, you might want to
+ // use pointers instead anyway.
+
+ int a = 5;
+ Any val(std::ref(a)); // int&
+ EXPECT_EQ(5, val.Get<std::reference_wrapper<int>>().get());
+ val.Get<std::reference_wrapper<int>>().get() = 7;
+ EXPECT_EQ(7, val.Get<std::reference_wrapper<int>>().get());
+ EXPECT_EQ(7, a);
+
+ Any val2(std::cref(a)); // const int&
+ EXPECT_EQ(7, val2.Get<std::reference_wrapper<const int>>().get());
+
+ a = 10;
+ EXPECT_EQ(10, val.Get<std::reference_wrapper<int>>().get());
+ EXPECT_EQ(10, val2.Get<std::reference_wrapper<const int>>().get());
+}
+
+TEST(Any, CustomTypes) {
+ struct Person {
+ std::string name;
+ int age;
+ };
+ Any val(Person{"Jack", 40}); // NOLINT(whitespace/braces)
+ Any val2 = val;
+ EXPECT_EQ("Jack", val.Get<Person>().name);
+ val.GetPtr<Person>()->name = "Joe";
+ val.GetPtr<Person>()->age /= 2;
+ EXPECT_EQ("Joe", val.Get<Person>().name);
+ EXPECT_EQ(20, val.Get<Person>().age);
+ EXPECT_EQ("Jack", val2.Get<Person>().name);
+ EXPECT_EQ(40, val2.Get<Person>().age);
+}
+
+TEST(Any, Swap) {
+ Any val(12);
+ Any val2(2.7);
+ EXPECT_EQ(12, val.Get<int>());
+ EXPECT_EQ(2.7, val2.Get<double>());
+
+ val.Swap(val2);
+ EXPECT_EQ(2.7, val.Get<double>());
+ EXPECT_EQ(12, val2.Get<int>());
+
+ std::swap(val, val2);
+ EXPECT_EQ(12, val.Get<int>());
+ EXPECT_EQ(2.7, val2.Get<double>());
+}
+
+TEST(Any, TypeMismatch) {
+ Any val(12);
+ EXPECT_DEATH(val.Get<double>(),
+ "Requesting value of type \\w+ from variant containing \\w+");
+
+ val = std::string("123");
+ EXPECT_DEATH(val.GetAsInteger(),
+ "Unable to convert value of type \\w+ to integer");
+
+ Any empty;
+ EXPECT_DEATH(empty.GetAsInteger(), "Must not be called on an empty Any");
+}
+
+TEST(Any, TryGet) {
+ Any val(12);
+ Any empty;
+ EXPECT_EQ("dummy", val.TryGet<std::string>("dummy"));
+ EXPECT_EQ(12, val.TryGet<int>(17));
+ EXPECT_EQ(17, empty.TryGet<int>(17));
+}