compressed_tuple_test.cc (14949B)
1 // Copyright 2018 The Abseil Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "absl/container/internal/compressed_tuple.h" 16 17 #include <memory> 18 #include <set> 19 #include <string> 20 #include <type_traits> 21 #include <utility> 22 #include <vector> 23 24 #include "gmock/gmock.h" 25 #include "gtest/gtest.h" 26 #include "absl/container/internal/test_instance_tracker.h" 27 #include "absl/memory/memory.h" 28 #include "absl/types/any.h" 29 #include "absl/types/optional.h" 30 #include "absl/utility/utility.h" 31 32 // These are declared at global scope purely so that error messages 33 // are smaller and easier to understand. 34 enum class CallType { kMutableRef, kConstRef, kMutableMove, kConstMove }; 35 36 template <int> 37 struct Empty { 38 constexpr CallType value() & { return CallType::kMutableRef; } 39 constexpr CallType value() const& { return CallType::kConstRef; } 40 constexpr CallType value() && { return CallType::kMutableMove; } 41 constexpr CallType value() const&& { return CallType::kConstMove; } 42 }; 43 44 // Unconditionally return an lvalue reference to `t`. 45 template <typename T> 46 constexpr T& AsLValue(T&& t) { 47 return t; 48 } 49 50 template <typename T> 51 struct NotEmpty { 52 T value; 53 }; 54 55 template <typename T, typename U> 56 struct TwoValues { 57 T value1; 58 U value2; 59 }; 60 61 62 namespace absl { 63 ABSL_NAMESPACE_BEGIN 64 namespace container_internal { 65 namespace { 66 67 using absl::test_internal::CopyableMovableInstance; 68 using absl::test_internal::InstanceTracker; 69 using ::testing::Each; 70 71 TEST(CompressedTupleTest, Sizeof) { 72 EXPECT_EQ(sizeof(int), sizeof(CompressedTuple<int>)); 73 EXPECT_EQ(sizeof(int), sizeof(CompressedTuple<int, Empty<0>>)); 74 EXPECT_EQ(sizeof(int), sizeof(CompressedTuple<int, Empty<0>, Empty<1>>)); 75 EXPECT_EQ(sizeof(int), 76 sizeof(CompressedTuple<int, Empty<0>, Empty<1>, Empty<2>>)); 77 78 EXPECT_EQ(sizeof(TwoValues<int, double>), 79 sizeof(CompressedTuple<int, NotEmpty<double>>)); 80 EXPECT_EQ(sizeof(TwoValues<int, double>), 81 sizeof(CompressedTuple<int, Empty<0>, NotEmpty<double>>)); 82 EXPECT_EQ(sizeof(TwoValues<int, double>), 83 sizeof(CompressedTuple<int, Empty<0>, NotEmpty<double>, Empty<1>>)); 84 } 85 86 TEST(CompressedTupleTest, PointerToEmpty) { 87 auto to_void_ptrs = [](const auto&... objs) { 88 return std::vector<const void*>{static_cast<const void*>(&objs)...}; 89 }; 90 { 91 using Tuple = CompressedTuple<int, Empty<0>>; 92 EXPECT_EQ(sizeof(int), sizeof(Tuple)); 93 Tuple t; 94 EXPECT_THAT(to_void_ptrs(t.get<1>()), Each(&t)); 95 } 96 { 97 using Tuple = CompressedTuple<int, Empty<0>, Empty<1>>; 98 EXPECT_EQ(sizeof(int), sizeof(Tuple)); 99 Tuple t; 100 EXPECT_THAT(to_void_ptrs(t.get<1>(), t.get<2>()), Each(&t)); 101 } 102 { 103 using Tuple = CompressedTuple<int, Empty<0>, Empty<1>, Empty<2>>; 104 EXPECT_EQ(sizeof(int), sizeof(Tuple)); 105 Tuple t; 106 EXPECT_THAT(to_void_ptrs(t.get<1>(), t.get<2>(), t.get<3>()), Each(&t)); 107 } 108 } 109 110 TEST(CompressedTupleTest, OneMoveOnRValueConstructionTemp) { 111 InstanceTracker tracker; 112 CompressedTuple<CopyableMovableInstance> x1(CopyableMovableInstance(1)); 113 EXPECT_EQ(tracker.instances(), 1); 114 EXPECT_EQ(tracker.copies(), 0); 115 EXPECT_LE(tracker.moves(), 1); 116 EXPECT_EQ(x1.get<0>().value(), 1); 117 } 118 119 TEST(CompressedTupleTest, OneMoveOnRValueConstructionMove) { 120 InstanceTracker tracker; 121 122 CopyableMovableInstance i1(1); 123 CompressedTuple<CopyableMovableInstance> x1(std::move(i1)); 124 EXPECT_EQ(tracker.instances(), 2); 125 EXPECT_EQ(tracker.copies(), 0); 126 EXPECT_LE(tracker.moves(), 1); 127 EXPECT_EQ(x1.get<0>().value(), 1); 128 } 129 130 TEST(CompressedTupleTest, OneMoveOnRValueConstructionMixedTypes) { 131 InstanceTracker tracker; 132 CopyableMovableInstance i1(1); 133 CopyableMovableInstance i2(2); 134 Empty<0> empty; 135 CompressedTuple<CopyableMovableInstance, CopyableMovableInstance&, Empty<0>> 136 x1(std::move(i1), i2, empty); 137 EXPECT_EQ(x1.get<0>().value(), 1); 138 EXPECT_EQ(x1.get<1>().value(), 2); 139 EXPECT_EQ(tracker.copies(), 0); 140 EXPECT_EQ(tracker.moves(), 1); 141 } 142 143 struct IncompleteType; 144 CompressedTuple<CopyableMovableInstance, IncompleteType&, Empty<0>> 145 MakeWithIncomplete(CopyableMovableInstance i1, 146 IncompleteType& t, // NOLINT 147 Empty<0> empty) { 148 return CompressedTuple<CopyableMovableInstance, IncompleteType&, Empty<0>>{ 149 std::move(i1), t, empty}; 150 } 151 152 struct IncompleteType {}; 153 TEST(CompressedTupleTest, OneMoveOnRValueConstructionWithIncompleteType) { 154 InstanceTracker tracker; 155 CopyableMovableInstance i1(1); 156 Empty<0> empty; 157 struct DerivedType : IncompleteType {int value = 0;}; 158 DerivedType fd; 159 fd.value = 7; 160 161 CompressedTuple<CopyableMovableInstance, IncompleteType&, Empty<0>> x1 = 162 MakeWithIncomplete(std::move(i1), fd, empty); 163 164 EXPECT_EQ(x1.get<0>().value(), 1); 165 EXPECT_EQ(static_cast<DerivedType&>(x1.get<1>()).value, 7); 166 167 EXPECT_EQ(tracker.copies(), 0); 168 EXPECT_EQ(tracker.moves(), 2); 169 } 170 171 TEST(CompressedTupleTest, 172 OneMoveOnRValueConstructionMixedTypes_BraceInitPoisonPillExpected) { 173 InstanceTracker tracker; 174 CopyableMovableInstance i1(1); 175 CopyableMovableInstance i2(2); 176 CompressedTuple<CopyableMovableInstance, CopyableMovableInstance&, Empty<0>> 177 x1(std::move(i1), i2, {}); // NOLINT 178 EXPECT_EQ(x1.get<0>().value(), 1); 179 EXPECT_EQ(x1.get<1>().value(), 2); 180 EXPECT_EQ(tracker.instances(), 3); 181 // We are forced into the `const Ts&...` constructor (invoking copies) 182 // because we need it to deduce the type of `{}`. 183 // std::tuple also has this behavior. 184 // Note, this test is proof that this is expected behavior, but it is not 185 // _desired_ behavior. 186 EXPECT_EQ(tracker.copies(), 1); 187 EXPECT_EQ(tracker.moves(), 0); 188 } 189 190 TEST(CompressedTupleTest, OneCopyOnLValueConstruction) { 191 InstanceTracker tracker; 192 CopyableMovableInstance i1(1); 193 194 CompressedTuple<CopyableMovableInstance> x1(i1); 195 EXPECT_EQ(tracker.copies(), 1); 196 EXPECT_EQ(tracker.moves(), 0); 197 198 tracker.ResetCopiesMovesSwaps(); 199 200 CopyableMovableInstance i2(2); 201 const CopyableMovableInstance& i2_ref = i2; 202 CompressedTuple<CopyableMovableInstance> x2(i2_ref); 203 EXPECT_EQ(tracker.copies(), 1); 204 EXPECT_EQ(tracker.moves(), 0); 205 } 206 207 TEST(CompressedTupleTest, OneMoveOnRValueAccess) { 208 InstanceTracker tracker; 209 CopyableMovableInstance i1(1); 210 CompressedTuple<CopyableMovableInstance> x(std::move(i1)); 211 tracker.ResetCopiesMovesSwaps(); 212 213 CopyableMovableInstance i2 = std::move(x).get<0>(); 214 EXPECT_EQ(tracker.copies(), 0); 215 EXPECT_EQ(tracker.moves(), 1); 216 } 217 218 TEST(CompressedTupleTest, OneCopyOnLValueAccess) { 219 InstanceTracker tracker; 220 221 CompressedTuple<CopyableMovableInstance> x(CopyableMovableInstance(0)); 222 EXPECT_EQ(tracker.copies(), 0); 223 EXPECT_EQ(tracker.moves(), 1); 224 225 CopyableMovableInstance t = x.get<0>(); 226 EXPECT_EQ(tracker.copies(), 1); 227 EXPECT_EQ(tracker.moves(), 1); 228 } 229 230 TEST(CompressedTupleTest, ZeroCopyOnRefAccess) { 231 InstanceTracker tracker; 232 233 CompressedTuple<CopyableMovableInstance> x(CopyableMovableInstance(0)); 234 EXPECT_EQ(tracker.copies(), 0); 235 EXPECT_EQ(tracker.moves(), 1); 236 237 CopyableMovableInstance& t1 = x.get<0>(); 238 const CopyableMovableInstance& t2 = x.get<0>(); 239 EXPECT_EQ(tracker.copies(), 0); 240 EXPECT_EQ(tracker.moves(), 1); 241 EXPECT_EQ(t1.value(), 0); 242 EXPECT_EQ(t2.value(), 0); 243 } 244 245 TEST(CompressedTupleTest, Access) { 246 struct S { 247 std::string x; 248 }; 249 CompressedTuple<int, Empty<0>, S> x(7, {}, S{"ABC"}); 250 EXPECT_EQ(sizeof(x), sizeof(TwoValues<int, S>)); 251 EXPECT_EQ(7, x.get<0>()); 252 EXPECT_EQ("ABC", x.get<2>().x); 253 } 254 255 TEST(CompressedTupleTest, NonClasses) { 256 CompressedTuple<int, const char*> x(7, "ABC"); 257 EXPECT_EQ(7, x.get<0>()); 258 EXPECT_STREQ("ABC", x.get<1>()); 259 } 260 261 TEST(CompressedTupleTest, MixClassAndNonClass) { 262 CompressedTuple<int, const char*, Empty<0>, NotEmpty<double>> x(7, "ABC", {}, 263 {1.25}); 264 struct Mock { 265 int v; 266 const char* p; 267 double d; 268 }; 269 EXPECT_EQ(sizeof(x), sizeof(Mock)); 270 EXPECT_EQ(7, x.get<0>()); 271 EXPECT_STREQ("ABC", x.get<1>()); 272 EXPECT_EQ(1.25, x.get<3>().value); 273 } 274 275 TEST(CompressedTupleTest, Nested) { 276 CompressedTuple<int, CompressedTuple<int>, 277 CompressedTuple<int, CompressedTuple<int>>> 278 x(1, CompressedTuple<int>(2), 279 CompressedTuple<int, CompressedTuple<int>>(3, CompressedTuple<int>(4))); 280 EXPECT_EQ(1, x.get<0>()); 281 EXPECT_EQ(2, x.get<1>().get<0>()); 282 EXPECT_EQ(3, x.get<2>().get<0>()); 283 EXPECT_EQ(4, x.get<2>().get<1>().get<0>()); 284 285 CompressedTuple<Empty<0>, Empty<0>, 286 CompressedTuple<Empty<0>, CompressedTuple<Empty<0>>>> 287 y; 288 std::set<Empty<0>*> empties{&y.get<0>(), &y.get<1>(), &y.get<2>().get<0>(), 289 &y.get<2>().get<1>().get<0>()}; 290 #ifdef _MSC_VER 291 // MSVC has a bug where many instances of the same base class are layed out in 292 // the same address when using __declspec(empty_bases). 293 // This will be fixed in a future version of MSVC. 294 int expected = 1; 295 #else 296 int expected = 4; 297 #endif 298 EXPECT_EQ(expected, sizeof(y)); 299 EXPECT_EQ(expected, empties.size()); 300 EXPECT_EQ(sizeof(y), sizeof(Empty<0>) * empties.size()); 301 302 EXPECT_EQ(4 * sizeof(char), 303 sizeof(CompressedTuple<CompressedTuple<char, char>, 304 CompressedTuple<char, char>>)); 305 EXPECT_TRUE((std::is_empty<CompressedTuple<Empty<0>, Empty<1>>>::value)); 306 307 // Make sure everything still works when things are nested. 308 struct CT_Empty : CompressedTuple<Empty<0>> {}; 309 CompressedTuple<Empty<0>, CT_Empty> nested_empty; 310 auto contained = nested_empty.get<0>(); 311 auto nested = nested_empty.get<1>().get<0>(); 312 EXPECT_TRUE((std::is_same<decltype(contained), decltype(nested)>::value)); 313 } 314 315 TEST(CompressedTupleTest, Reference) { 316 int i = 7; 317 std::string s = "Very long string that goes in the heap"; 318 CompressedTuple<int, int&, std::string, std::string&> x(i, i, s, s); 319 320 // Sanity check. We should have not moved from `s` 321 EXPECT_EQ(s, "Very long string that goes in the heap"); 322 323 EXPECT_EQ(x.get<0>(), x.get<1>()); 324 EXPECT_NE(&x.get<0>(), &x.get<1>()); 325 EXPECT_EQ(&x.get<1>(), &i); 326 327 EXPECT_EQ(x.get<2>(), x.get<3>()); 328 EXPECT_NE(&x.get<2>(), &x.get<3>()); 329 EXPECT_EQ(&x.get<3>(), &s); 330 } 331 332 TEST(CompressedTupleTest, NoElements) { 333 CompressedTuple<> x; 334 static_cast<void>(x); // Silence -Wunused-variable. 335 EXPECT_TRUE(std::is_empty<CompressedTuple<>>::value); 336 } 337 338 TEST(CompressedTupleTest, MoveOnlyElements) { 339 CompressedTuple<std::unique_ptr<std::string>> str_tup( 340 absl::make_unique<std::string>("str")); 341 342 CompressedTuple<CompressedTuple<std::unique_ptr<std::string>>, 343 std::unique_ptr<int>> 344 x(std::move(str_tup), absl::make_unique<int>(5)); 345 346 EXPECT_EQ(*x.get<0>().get<0>(), "str"); 347 EXPECT_EQ(*x.get<1>(), 5); 348 349 std::unique_ptr<std::string> x0 = std::move(x.get<0>()).get<0>(); 350 std::unique_ptr<int> x1 = std::move(x).get<1>(); 351 352 EXPECT_EQ(*x0, "str"); 353 EXPECT_EQ(*x1, 5); 354 } 355 356 TEST(CompressedTupleTest, MoveConstructionMoveOnlyElements) { 357 CompressedTuple<std::unique_ptr<std::string>> base( 358 absl::make_unique<std::string>("str")); 359 EXPECT_EQ(*base.get<0>(), "str"); 360 361 CompressedTuple<std::unique_ptr<std::string>> copy(std::move(base)); 362 EXPECT_EQ(*copy.get<0>(), "str"); 363 } 364 365 TEST(CompressedTupleTest, AnyElements) { 366 any a(std::string("str")); 367 CompressedTuple<any, any&> x(any(5), a); 368 EXPECT_EQ(absl::any_cast<int>(x.get<0>()), 5); 369 EXPECT_EQ(absl::any_cast<std::string>(x.get<1>()), "str"); 370 371 a = 0.5f; 372 EXPECT_EQ(absl::any_cast<float>(x.get<1>()), 0.5); 373 } 374 375 TEST(CompressedTupleTest, Constexpr) { 376 struct NonTrivialStruct { 377 constexpr NonTrivialStruct() = default; 378 constexpr int value() const { return v; } 379 int v = 5; 380 }; 381 struct TrivialStruct { 382 TrivialStruct() = default; 383 constexpr int value() const { return v; } 384 int v; 385 }; 386 387 using Tuple = CompressedTuple<int, double, CompressedTuple<int>, Empty<0>>; 388 389 constexpr Tuple x(7, 1.25, CompressedTuple<int>(5), {}); 390 constexpr int x0 = x.get<0>(); 391 constexpr double x1 = x.get<1>(); 392 constexpr int x2 = x.get<2>().get<0>(); 393 constexpr CallType x3 = x.get<3>().value(); 394 395 EXPECT_EQ(x0, 7); 396 EXPECT_EQ(x1, 1.25); 397 EXPECT_EQ(x2, 5); 398 EXPECT_EQ(x3, CallType::kConstRef); 399 400 constexpr int m0 = Tuple(5, 0.25, CompressedTuple<int>(3), {}).get<0>(); 401 constexpr double m1 = Tuple(5, 0.25, CompressedTuple<int>(3), {}).get<1>(); 402 constexpr int m2 = 403 Tuple(5, 0.25, CompressedTuple<int>(3), {}).get<2>().get<0>(); 404 constexpr CallType m3 = 405 Tuple(5, 0.25, CompressedTuple<int>(3), {}).get<3>().value(); 406 407 EXPECT_EQ(m0, 5); 408 EXPECT_EQ(m1, 0.25); 409 EXPECT_EQ(m2, 3); 410 EXPECT_EQ(m3, CallType::kMutableMove); 411 412 constexpr CompressedTuple<Empty<0>, TrivialStruct, int> trivial = {}; 413 constexpr CallType trivial0 = trivial.get<0>().value(); 414 constexpr int trivial1 = trivial.get<1>().value(); 415 constexpr int trivial2 = trivial.get<2>(); 416 417 EXPECT_EQ(trivial0, CallType::kConstRef); 418 EXPECT_EQ(trivial1, 0); 419 EXPECT_EQ(trivial2, 0); 420 421 constexpr CompressedTuple<Empty<0>, NonTrivialStruct, absl::optional<int>> 422 non_trivial = {}; 423 constexpr CallType non_trivial0 = non_trivial.get<0>().value(); 424 constexpr int non_trivial1 = non_trivial.get<1>().value(); 425 constexpr absl::optional<int> non_trivial2 = non_trivial.get<2>(); 426 427 EXPECT_EQ(non_trivial0, CallType::kConstRef); 428 EXPECT_EQ(non_trivial1, 5); 429 EXPECT_EQ(non_trivial2, absl::nullopt); 430 431 static constexpr char data[] = "DEF"; 432 constexpr CompressedTuple<const char*> z(data); 433 constexpr const char* z1 = z.get<0>(); 434 EXPECT_EQ(std::string(z1), std::string(data)); 435 436 #if defined(__clang__) 437 // An apparent bug in earlier versions of gcc claims these are ambiguous. 438 constexpr int x2m = std::move(x.get<2>()).get<0>(); 439 constexpr CallType x3m = std::move(x).get<3>().value(); 440 EXPECT_EQ(x2m, 5); 441 EXPECT_EQ(x3m, CallType::kConstMove); 442 #endif 443 } 444 445 #if defined(__clang__) || defined(__GNUC__) 446 TEST(CompressedTupleTest, EmptyFinalClass) { 447 struct S final { 448 int f() const { return 5; } 449 }; 450 CompressedTuple<S> x; 451 EXPECT_EQ(x.get<0>().f(), 5); 452 } 453 #endif 454 455 // TODO(b/214288561): enable this test. 456 TEST(CompressedTupleTest, DISABLED_NestedEbo) { 457 struct Empty1 {}; 458 struct Empty2 {}; 459 CompressedTuple<Empty1, CompressedTuple<Empty2>, int> x; 460 CompressedTuple<Empty1, Empty2, int> y; 461 // Currently fails with sizeof(x) == 8, sizeof(y) == 4. 462 EXPECT_EQ(sizeof(x), sizeof(y)); 463 } 464 465 } // namespace 466 } // namespace container_internal 467 ABSL_NAMESPACE_END 468 } // namespace absl