| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| #include "arrow/util/variant.h" |
| |
| #include <functional> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "arrow/testing/gtest_compat.h" |
| |
| namespace arrow { |
| |
| namespace util { |
| namespace { |
| |
| using ::testing::Eq; |
| |
| template <typename H, typename... T> |
| void AssertDefaultConstruction() { |
| using variant_type = Variant<H, T...>; |
| |
| static_assert(std::is_nothrow_default_constructible<variant_type>::value, ""); |
| |
| variant_type v; |
| EXPECT_EQ(v.index(), 0); |
| EXPECT_EQ(get<H>(v), H{}); |
| } |
| |
| TEST(Variant, DefaultConstruction) { |
| AssertDefaultConstruction<int>(); |
| AssertDefaultConstruction<int, std::string>(); |
| AssertDefaultConstruction<std::string, int>(); |
| AssertDefaultConstruction<std::unique_ptr<int>>(); |
| AssertDefaultConstruction<std::vector<int>, int>(); |
| AssertDefaultConstruction<bool, std::string, std::unique_ptr<int>, void*, |
| std::true_type>(); |
| AssertDefaultConstruction<std::nullptr_t, std::unique_ptr<int>, void*, bool, |
| std::string, std::true_type>(); |
| } |
| |
| template <typename V, typename T> |
| struct AssertCopyConstructionOne { |
| void operator()(uint8_t index) { |
| V v{member_}; |
| EXPECT_EQ(v.index(), index); |
| EXPECT_EQ(get<T>(v), member_); |
| |
| V copy{v}; |
| EXPECT_EQ(copy.index(), v.index()); |
| EXPECT_EQ(get<T>(copy), get<T>(v)); |
| EXPECT_EQ(copy, v); |
| |
| V assigned; |
| assigned = member_; |
| EXPECT_EQ(assigned.index(), index); |
| EXPECT_EQ(get<T>(assigned), member_); |
| |
| assigned = v; |
| EXPECT_EQ(assigned.index(), v.index()); |
| EXPECT_EQ(get<T>(assigned), get<T>(v)); |
| EXPECT_EQ(assigned, v); |
| } |
| |
| const T& member_; |
| }; |
| |
| template <typename... T> |
| void AssertCopyConstruction(T... member) { |
| uint8_t index = 0; |
| for (auto Assert : {std::function<void(uint8_t)>( |
| AssertCopyConstructionOne<Variant<T...>, T>{member})...}) { |
| Assert(index++); |
| } |
| } |
| |
| template <typename... T> |
| void AssertCopyConstructionDisabled() { |
| static_assert(!std::is_copy_constructible<Variant<T...>>::value, |
| "copy construction was not disabled"); |
| } |
| |
| TEST(Variant, CopyConstruction) { |
| // if any member is not copy constructible then Variant is not copy constructible |
| AssertCopyConstructionDisabled<std::unique_ptr<int>>(); |
| AssertCopyConstructionDisabled<std::unique_ptr<int>, std::string>(); |
| AssertCopyConstructionDisabled<std::string, int, bool, std::unique_ptr<int>>(); |
| |
| AssertCopyConstruction(32, std::string("hello"), true); |
| AssertCopyConstruction(std::string("world"), false, 53); |
| AssertCopyConstruction(nullptr, std::true_type{}, std::string("!")); |
| AssertCopyConstruction(std::vector<int>{1, 3, 3, 7}, "C string"); |
| |
| // copy assignment operator is not used |
| struct CopyAssignThrows { |
| CopyAssignThrows() = default; |
| CopyAssignThrows(const CopyAssignThrows&) = default; |
| |
| CopyAssignThrows& operator=(const CopyAssignThrows&) { throw 42; } |
| |
| CopyAssignThrows(CopyAssignThrows&&) = default; |
| CopyAssignThrows& operator=(CopyAssignThrows&&) = default; |
| |
| bool operator==(const CopyAssignThrows&) const { return true; } |
| }; |
| EXPECT_NO_THROW(AssertCopyConstruction(CopyAssignThrows{})); |
| } |
| |
| TEST(Variant, Emplace) { |
| using variant_type = Variant<std::string, std::vector<int>, int>; |
| variant_type v; |
| |
| v.emplace<int>(); |
| EXPECT_EQ(v, variant_type{int{}}); |
| |
| v.emplace<std::string>("hello"); |
| EXPECT_EQ(v, variant_type{std::string("hello")}); |
| |
| v.emplace<std::vector<int>>({1, 3, 3, 7}); |
| EXPECT_EQ(v, variant_type{std::vector<int>({1, 3, 3, 7})}); |
| } |
| |
| TEST(Variant, MoveConstruction) { |
| struct noop_delete { |
| void operator()(...) const {} |
| }; |
| using ptr = std::unique_ptr<int, noop_delete>; |
| static_assert(!std::is_copy_constructible<ptr>::value, ""); |
| |
| using variant_type = Variant<int, ptr>; |
| |
| int tag = 42; |
| auto ExpectIsTag = [&](const variant_type& v) { |
| EXPECT_EQ(v.index(), 1); |
| EXPECT_EQ(get<ptr>(v).get(), &tag); |
| }; |
| |
| ptr p; |
| |
| // move construction from member |
| p.reset(&tag); |
| variant_type v0{std::move(p)}; |
| ExpectIsTag(v0); |
| |
| // move assignment from member |
| p.reset(&tag); |
| v0 = std::move(p); |
| ExpectIsTag(v0); |
| |
| // move construction from other variant |
| variant_type v1{std::move(v0)}; |
| ExpectIsTag(v1); |
| |
| // move assignment from other variant |
| p.reset(&tag); |
| variant_type v2{std::move(p)}; |
| v1 = std::move(v2); |
| ExpectIsTag(v1); |
| |
| // type changing move assignment from member |
| variant_type v3; |
| EXPECT_NE(v3.index(), 1); |
| p.reset(&tag); |
| v3 = std::move(p); |
| ExpectIsTag(v3); |
| |
| // type changing move assignment from other variant |
| variant_type v4; |
| EXPECT_NE(v4.index(), 1); |
| v4 = std::move(v3); |
| ExpectIsTag(v4); |
| } |
| |
| TEST(Variant, ExceptionSafety) { |
| struct { |
| } actually_throw; |
| |
| struct { |
| } dont_throw; |
| |
| struct ConstructorThrows { |
| explicit ConstructorThrows(decltype(actually_throw)) { throw 42; } |
| explicit ConstructorThrows(decltype(dont_throw)) {} |
| |
| ConstructorThrows(const ConstructorThrows&) { throw 42; } |
| |
| ConstructorThrows& operator=(const ConstructorThrows&) = default; |
| ConstructorThrows(ConstructorThrows&&) = default; |
| ConstructorThrows& operator=(ConstructorThrows&&) = default; |
| }; |
| |
| Variant<int, ConstructorThrows> v; |
| |
| // constructor throws during emplacement |
| EXPECT_THROW(v.emplace<ConstructorThrows>(actually_throw), int); |
| // safely returned to the default state |
| EXPECT_EQ(v.index(), 0); |
| |
| // constructor throws during copy assignment from member |
| EXPECT_THROW( |
| { |
| const ConstructorThrows throws(dont_throw); |
| v = throws; |
| }, |
| int); |
| // safely returned to the default state |
| EXPECT_EQ(v.index(), 0); |
| } |
| |
| template <typename V, typename T> |
| struct AssertVisitOne { |
| void operator()(const T& actual) { EXPECT_EQ(&actual, expected_); } |
| |
| void operator()(T* actual) { EXPECT_EQ(actual, expected_); } |
| |
| template <typename U> |
| void operator()(const U&) { |
| FAIL() << "the expected type was not visited."; |
| } |
| |
| template <typename U> |
| void operator()(U*) { |
| FAIL() << "the expected type was not visited."; |
| } |
| |
| explicit AssertVisitOne(T member) : member_(std::move(member)) {} |
| |
| void operator()() { |
| V v{member_}; |
| expected_ = &get<T>(v); |
| visit(*this, v); |
| visit(*this, &v); |
| } |
| |
| T member_; |
| const T* expected_; |
| }; |
| |
| template <typename... T> |
| void AssertVisit(T... member) { |
| for (auto Assert : |
| {std::function<void()>(AssertVisitOne<Variant<T...>, T>{member})...}) { |
| Assert(); |
| } |
| } |
| |
| TEST(VariantTest, Visit) { |
| AssertVisit(32, std::string("hello"), true); |
| AssertVisit(std::string("world"), false, 53); |
| AssertVisit(nullptr, std::true_type{}, std::string("!")); |
| AssertVisit(std::vector<int>{1, 3, 3, 7}, "C string"); |
| |
| using int_or_string = Variant<int, std::string>; |
| int_or_string v; |
| |
| // value returning visit: |
| struct { |
| int_or_string operator()(int i) { return int_or_string{i * 2}; } |
| int_or_string operator()(const std::string& s) { return int_or_string{s + s}; } |
| } Double; |
| |
| v = 7; |
| EXPECT_EQ(visit(Double, v), int_or_string{14}); |
| |
| v = "lolol"; |
| EXPECT_EQ(visit(Double, v), int_or_string{"lolollolol"}); |
| |
| // mutating visit: |
| struct { |
| void operator()(int* i) { *i *= 2; } |
| void operator()(std::string* s) { *s += *s; } |
| } DoubleInplace; |
| |
| v = 7; |
| visit(DoubleInplace, &v); |
| EXPECT_EQ(v, int_or_string{14}); |
| |
| v = "lolol"; |
| visit(DoubleInplace, &v); |
| EXPECT_EQ(v, int_or_string{"lolollolol"}); |
| } |
| |
| TEST(VariantTest, Equality) { |
| using int_or_double = Variant<int, double>; |
| |
| auto eq = [](const int_or_double& a, const int_or_double& b) { |
| EXPECT_TRUE(a == b); |
| EXPECT_FALSE(a != b); |
| }; |
| auto ne = [](const int_or_double& a, const int_or_double& b) { |
| EXPECT_TRUE(a != b); |
| EXPECT_FALSE(a == b); |
| }; |
| |
| int_or_double u, v; |
| u.emplace<int>(1); |
| v.emplace<int>(1); |
| eq(u, v); |
| v.emplace<int>(2); |
| ne(u, v); |
| v.emplace<double>(1.0); |
| ne(u, v); |
| u.emplace<double>(1.0); |
| eq(u, v); |
| u.emplace<double>(2.0); |
| ne(u, v); |
| } |
| |
| } // namespace |
| } // namespace util |
| } // namespace arrow |