From 2125e6444a9de9e41f21ecdc674dd7d8759c149d Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Wed, 1 Aug 2018 04:34:12 -0700 Subject: Export of internal Abseil changes. -- ac7508120c60dfe689c40929e416b6a486f83ee3 by Gennadiy Rozental : Internal change PiperOrigin-RevId: 206912089 -- bd709faba88565367b6d337466e6456481b5f3e8 by Matt Calabrese : Implement `std::experimental::is_detected` in type_traits internals and move `is_detected_convertible` from variant's internals to type_traits internals. This is in preparation of creating workarounds for broken standard traits. PiperOrigin-RevId: 206825598 -- 0dbddea569370eb9b6348cee172d1874f9046eb4 by Jorg Brown : Support users who turn on floating-point conversion warnings PiperOrigin-RevId: 206813209 -- 30991f757c8f0100584619d8a9c41897d029f112 by Jorg Brown : Speed up the absl::Seconds() function for floating-point values, roughly by 4.5x, since we can take advantage of the fact that we're just taking a floating-point number and splitting it into its integral and fractional parts. PiperOrigin-RevId: 206806270 -- 6883837176838aa5a517e7a8cb4c99afd24c0d12 by Jon Cohen : Remove the DISABLE_INSTALL from absl_container. It doesn't do anything. PiperOrigin-RevId: 206802544 -- 92ab14fed06e6dd1f01a0284bd7f95d3e2c0c3d8 by Jon Cohen : Internal change PiperOrigin-RevId: 206776244 -- 17b76c7f364ac562d9e0faeca0320f63aa3fdb85 by Jorg Brown : Fix absl/strings:numbers_test flakiness due to exceeding the 1-minute timeout PiperOrigin-RevId: 206763175 -- 6637843f2e198b8efd90e5577fbc86bdea43b2cc by Abseil Team : Adds templated allocator to absl::FixedArray with corresponding tests PiperOrigin-RevId: 206354178 -- bced22f81add828c9b4c60eb45554d36c22e2f96 by Abseil Team : Adds templated allocator to absl::FixedArray with corresponding tests PiperOrigin-RevId: 206347377 -- 75be14a71d2d5e335812d5b7670120271fb5bd79 by Abseil Team : Internal change. PiperOrigin-RevId: 206326935 -- 6929e43f4c7898b1f51e441911a19092a06fbf97 by Abseil Team : Adds templated allocator to absl::FixedArray with corresponding tests PiperOrigin-RevId: 206326368 -- 55ae34b75ff029eb267f9519e577bab8a575b487 by Abseil Team : Internal change. PiperOrigin-RevId: 206233448 -- 6950a8ccddf35d451eec2d02cd28a797c8b7cf6a by Matt Kulukundis : Internal change PiperOrigin-RevId: 206035613 GitOrigin-RevId: ac7508120c60dfe689c40929e416b6a486f83ee3 Change-Id: I675605abbedab6b3ac9aa82195cbd059ff7c82b1 --- absl/container/BUILD.bazel | 20 +++ absl/container/CMakeLists.txt | 1 - absl/container/fixed_array.h | 154 +++++++++------- absl/container/fixed_array_test.cc | 213 ++++++++++++++++++++++- absl/container/internal/compressed_tuple.h | 175 +++++++++++++++++++ absl/container/internal/compressed_tuple_test.cc | 166 ++++++++++++++++++ absl/memory/memory.h | 56 ++++-- absl/memory/memory_exception_safety_test.cc | 11 -- absl/memory/memory_test.cc | 43 ----- absl/meta/type_traits.h | 44 +++++ absl/meta/type_traits_test.cc | 81 ++++++++- absl/strings/numbers_test.cc | 7 +- absl/time/duration.cc | 5 +- absl/time/duration_benchmark.cc | 78 ++++++++- absl/time/duration_test.cc | 124 +++++++++++-- absl/time/time.h | 97 ++++++----- absl/types/internal/variant.h | 28 +-- 17 files changed, 1066 insertions(+), 237 deletions(-) create mode 100644 absl/container/internal/compressed_tuple.h create mode 100644 absl/container/internal/compressed_tuple_test.cc diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 07df3675..6d5c958f 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -25,11 +25,31 @@ package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Apache 2.0 +cc_library( + name = "compressed_tuple", + hdrs = ["internal/compressed_tuple.h"], + copts = ABSL_DEFAULT_COPTS, + deps = [ + "//absl/utility", + ], +) + +cc_test( + name = "compressed_tuple_test", + srcs = ["internal/compressed_tuple_test.cc"], + copts = ABSL_TEST_COPTS, + deps = [ + ":compressed_tuple", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "fixed_array", hdrs = ["fixed_array.h"], copts = ABSL_DEFAULT_COPTS, deps = [ + ":compressed_tuple", "//absl/algorithm", "//absl/base:core_headers", "//absl/base:dynamic_annotations", diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index d580b489..123e4c48 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -52,7 +52,6 @@ absl_library( ${TEST_INSTANCE_TRACKER_LIB_SRC} PUBLIC_LIBRARIES absl::container - DISABLE_INSTALL ) diff --git a/absl/container/fixed_array.h b/absl/container/fixed_array.h index 62600df0..f480047a 100644 --- a/absl/container/fixed_array.h +++ b/absl/container/fixed_array.h @@ -47,6 +47,7 @@ #include "absl/base/macros.h" #include "absl/base/optimization.h" #include "absl/base/port.h" +#include "absl/container/internal/compressed_tuple.h" #include "absl/memory/memory.h" namespace absl { @@ -76,73 +77,99 @@ constexpr static auto kFixedArrayUseDefault = static_cast(-1); // heap allocation, it will do so with global `::operator new[]()` and // `::operator delete[]()`, even if T provides class-scope overrides for these // operators. -template +template > class FixedArray { static_assert(!std::is_array::value || std::extent::value > 0, "Arrays with unknown bounds cannot be used with FixedArray."); + static constexpr size_t kInlineBytesDefault = 256; + using AllocatorTraits = std::allocator_traits; // std::iterator_traits isn't guaranteed to be SFINAE-friendly until C++17, // but this seems to be mostly pedantic. template using EnableIfForwardIterator = absl::enable_if_t::iterator_category, std::forward_iterator_tag>::value>; + static constexpr bool NoexceptCopyable() { + return std::is_nothrow_copy_constructible::value && + absl::allocator_is_nothrow::value; + } + static constexpr bool NoexceptMovable() { + return std::is_nothrow_move_constructible::value && + absl::allocator_is_nothrow::value; + } + static constexpr bool DefaultConstructorIsNonTrivial() { + return !absl::is_trivially_default_constructible::value; + } public: - using value_type = T; - using iterator = T*; - using const_iterator = const T*; + using allocator_type = typename AllocatorTraits::allocator_type; + using value_type = typename allocator_type::value_type; + using pointer = typename allocator_type::pointer; + using const_pointer = typename allocator_type::const_pointer; + using reference = typename allocator_type::reference; + using const_reference = typename allocator_type::const_reference; + using size_type = typename allocator_type::size_type; + using difference_type = typename allocator_type::difference_type; + using iterator = pointer; + using const_iterator = const_pointer; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - using reference = T&; - using const_reference = const T&; - using pointer = T*; - using const_pointer = const T*; - using difference_type = ptrdiff_t; - using size_type = size_t; static constexpr size_type inline_elements = - inlined == kFixedArrayUseDefault - ? kInlineBytesDefault / sizeof(value_type) - : inlined; + (N == kFixedArrayUseDefault ? kInlineBytesDefault / sizeof(value_type) + : static_cast(N)); - FixedArray(const FixedArray& other) - : FixedArray(other.begin(), other.end()) {} + FixedArray( + const FixedArray& other, + const allocator_type& a = allocator_type()) noexcept(NoexceptCopyable()) + : FixedArray(other.begin(), other.end(), a) {} - FixedArray(FixedArray&& other) noexcept( - absl::conjunction>, - std::is_nothrow_move_constructible>::value) + FixedArray( + FixedArray&& other, + const allocator_type& a = allocator_type()) noexcept(NoexceptMovable()) : FixedArray(std::make_move_iterator(other.begin()), - std::make_move_iterator(other.end())) {} + std::make_move_iterator(other.end()), a) {} // Creates an array object that can store `n` elements. // Note that trivially constructible elements will be uninitialized. - explicit FixedArray(size_type n) : storage_(n) { - absl::memory_internal::uninitialized_default_construct_n(storage_.begin(), - size()); + explicit FixedArray(size_type n, const allocator_type& a = allocator_type()) + : storage_(n, a) { + if (DefaultConstructorIsNonTrivial()) { + memory_internal::ConstructStorage(storage_.alloc(), storage_.begin(), + storage_.end()); + } } // Creates an array initialized with `n` copies of `val`. - FixedArray(size_type n, const value_type& val) : storage_(n) { - std::uninitialized_fill_n(data(), size(), val); + FixedArray(size_type n, const value_type& val, + const allocator_type& a = allocator_type()) + : storage_(n, a) { + memory_internal::ConstructStorage(storage_.alloc(), storage_.begin(), + storage_.end(), val); } + // Creates an array initialized with the size and contents of `init_list`. + FixedArray(std::initializer_list init_list, + const allocator_type& a = allocator_type()) + : FixedArray(init_list.begin(), init_list.end(), a) {} + // Creates an array initialized with the elements from the input // range. The array's size will always be `std::distance(first, last)`. // REQUIRES: Iterator must be a forward_iterator or better. template * = nullptr> - FixedArray(Iterator first, Iterator last) - : storage_(std::distance(first, last)) { - std::uninitialized_copy(first, last, data()); + FixedArray(Iterator first, Iterator last, + const allocator_type& a = allocator_type()) + : storage_(std::distance(first, last), a) { + memory_internal::CopyToStorageFromRange(storage_.alloc(), storage_.begin(), + first, last); } - FixedArray(std::initializer_list init_list) - : FixedArray(init_list.begin(), init_list.end()) {} - ~FixedArray() noexcept { - for (const StorageElement& cur : storage_) { - cur.~StorageElement(); + for (auto* cur = storage_.begin(); cur != storage_.end(); ++cur) { + AllocatorTraits::destroy(*storage_.alloc(), cur); } } @@ -332,7 +359,6 @@ class FixedArray { friend bool operator>=(const FixedArray& lhs, const FixedArray& rhs) { return !(lhs < rhs); } - private: // StorageElement // @@ -364,6 +390,8 @@ class FixedArray { using StorageElement = absl::conditional_t::value, StorageElementWrapper, value_type>; + using StorageElementBuffer = + absl::aligned_storage_t; static pointer AsValueType(pointer ptr) { return ptr; } static pointer AsValueType(StorageElementWrapper* ptr) { @@ -374,9 +402,6 @@ class FixedArray { static_assert(alignof(StorageElement) == alignof(value_type), ""); struct NonEmptyInlinedStorage { - using StorageElementBuffer = - absl::aligned_storage_t; StorageElement* data() { return reinterpret_cast(inlined_storage_.data()); } @@ -386,8 +411,8 @@ class FixedArray { void* RedzoneEnd() { return &redzone_end_ + 1; } #endif // ADDRESS_SANITIZER - void AnnotateConstruct(size_t); - void AnnotateDestruct(size_t); + void AnnotateConstruct(size_type); + void AnnotateDestruct(size_type); ADDRESS_SANITIZER_REDZONE(redzone_begin_); std::array inlined_storage_; @@ -396,8 +421,8 @@ class FixedArray { struct EmptyInlinedStorage { StorageElement* data() { return nullptr; } - void AnnotateConstruct(size_t) {} - void AnnotateDestruct(size_t) {} + void AnnotateConstruct(size_type) {} + void AnnotateDestruct(size_type) {} }; using InlinedStorage = @@ -414,48 +439,57 @@ class FixedArray { // class Storage : public InlinedStorage { public: - explicit Storage(size_type n) : data_(CreateStorage(n)), size_(n) {} + Storage(size_type n, const allocator_type& a) + : size_alloc_(n, a), data_(InitializeData()) {} + ~Storage() noexcept { if (UsingInlinedStorage(size())) { - this->AnnotateDestruct(size()); + InlinedStorage::AnnotateDestruct(size()); } else { - std::allocator().deallocate(begin(), size()); + AllocatorTraits::deallocate(*alloc(), AsValueType(begin()), size()); } } - size_type size() const { return size_; } + size_type size() const { return size_alloc_.template get<0>(); } StorageElement* begin() const { return data_; } StorageElement* end() const { return begin() + size(); } + allocator_type* alloc() { + return std::addressof(size_alloc_.template get<1>()); + } private: static bool UsingInlinedStorage(size_type n) { return n <= inline_elements; } - StorageElement* CreateStorage(size_type n) { - if (UsingInlinedStorage(n)) { - this->AnnotateConstruct(n); + StorageElement* InitializeData() { + if (UsingInlinedStorage(size())) { + InlinedStorage::AnnotateConstruct(size()); return InlinedStorage::data(); } else { - return std::allocator().allocate(n); + return reinterpret_cast( + AllocatorTraits::allocate(*alloc(), size())); } } - StorageElement* const data_; - const size_type size_; + // `CompressedTuple` takes advantage of EBCO for stateless `allocator_type`s + container_internal::CompressedTuple size_alloc_; + StorageElement* data_; }; - const Storage storage_; + Storage storage_; }; -template -constexpr size_t FixedArray::inline_elements; +template +constexpr size_t FixedArray::kInlineBytesDefault; -template -constexpr size_t FixedArray::kInlineBytesDefault; +template +constexpr typename FixedArray::size_type + FixedArray::inline_elements; -template -void FixedArray::NonEmptyInlinedStorage::AnnotateConstruct(size_t n) { +template +void FixedArray::NonEmptyInlinedStorage::AnnotateConstruct( + typename FixedArray::size_type n) { #ifdef ADDRESS_SANITIZER if (!n) return; ANNOTATE_CONTIGUOUS_CONTAINER(data(), RedzoneEnd(), RedzoneEnd(), data() + n); @@ -464,8 +498,9 @@ void FixedArray::NonEmptyInlinedStorage::AnnotateConstruct(size_t n) { static_cast(n); // Mark used when not in asan mode } -template -void FixedArray::NonEmptyInlinedStorage::AnnotateDestruct(size_t n) { +template +void FixedArray::NonEmptyInlinedStorage::AnnotateDestruct( + typename FixedArray::size_type n) { #ifdef ADDRESS_SANITIZER if (!n) return; ANNOTATE_CONTIGUOUS_CONTAINER(data(), RedzoneEnd(), data() + n, RedzoneEnd()); @@ -473,6 +508,5 @@ void FixedArray::NonEmptyInlinedStorage::AnnotateDestruct(size_t n) { #endif // ADDRESS_SANITIZER static_cast(n); // Mark used when not in asan mode } - } // namespace absl #endif // ABSL_CONTAINER_FIXED_ARRAY_H_ diff --git a/absl/container/fixed_array_test.cc b/absl/container/fixed_array_test.cc index 2142132d..b07ebcb6 100644 --- a/absl/container/fixed_array_test.cc +++ b/absl/container/fixed_array_test.cc @@ -15,9 +15,11 @@ #include "absl/container/fixed_array.h" #include +#include #include #include #include +#include #include #include #include @@ -607,6 +609,216 @@ TEST(FixedArrayTest, Fill) { empty.fill(fill_val); } +// TODO(johnsoncj): Investigate InlinedStorage default initialization in GCC 4.x +#ifndef __GNUC__ +TEST(FixedArrayTest, DefaultCtorDoesNotValueInit) { + using T = char; + constexpr auto capacity = 10; + using FixedArrType = absl::FixedArray; + using FixedArrBuffType = + absl::aligned_storage_t; + constexpr auto scrubbed_bits = 0x95; + constexpr auto length = capacity / 2; + + FixedArrBuffType buff; + std::memset(std::addressof(buff), scrubbed_bits, sizeof(FixedArrBuffType)); + + FixedArrType* arr = + ::new (static_cast(std::addressof(buff))) FixedArrType(length); + EXPECT_THAT(*arr, testing::Each(scrubbed_bits)); + arr->~FixedArrType(); +} +#endif // __GNUC__ + +// This is a stateful allocator, but the state lives outside of the +// allocator (in whatever test is using the allocator). This is odd +// but helps in tests where the allocator is propagated into nested +// containers - that chain of allocators uses the same state and is +// thus easier to query for aggregate allocation information. +template +class CountingAllocator : public std::allocator { + public: + using Alloc = std::allocator; + using pointer = typename Alloc::pointer; + using size_type = typename Alloc::size_type; + + CountingAllocator() : bytes_used_(nullptr), instance_count_(nullptr) {} + explicit CountingAllocator(int64_t* b) + : bytes_used_(b), instance_count_(nullptr) {} + CountingAllocator(int64_t* b, int64_t* a) + : bytes_used_(b), instance_count_(a) {} + + template + explicit CountingAllocator(const CountingAllocator& x) + : Alloc(x), + bytes_used_(x.bytes_used_), + instance_count_(x.instance_count_) {} + + pointer allocate(size_type n, const void* const hint = nullptr) { + assert(bytes_used_ != nullptr); + *bytes_used_ += n * sizeof(T); + return Alloc::allocate(n, hint); + } + + void deallocate(pointer p, size_type n) { + Alloc::deallocate(p, n); + assert(bytes_used_ != nullptr); + *bytes_used_ -= n * sizeof(T); + } + + template + void construct(pointer p, Args&&... args) { + Alloc::construct(p, absl::forward(args)...); + if (instance_count_) { + *instance_count_ += 1; + } + } + + void destroy(pointer p) { + Alloc::destroy(p); + if (instance_count_) { + *instance_count_ -= 1; + } + } + + template + class rebind { + public: + using other = CountingAllocator; + }; + + int64_t* bytes_used_; + int64_t* instance_count_; +}; + +TEST(AllocatorSupportTest, CountInlineAllocations) { + constexpr size_t inlined_size = 4; + using Alloc = CountingAllocator; + using AllocFxdArr = absl::FixedArray; + + int64_t allocated = 0; + int64_t active_instances = 0; + + { + const int ia[] = {0, 1, 2, 3, 4, 5, 6, 7}; + + Alloc alloc(&allocated, &active_instances); + + AllocFxdArr arr(ia, ia + inlined_size, alloc); + static_cast(arr); + } + + EXPECT_EQ(allocated, 0); + EXPECT_EQ(active_instances, 0); +} + +TEST(AllocatorSupportTest, CountOutoflineAllocations) { + constexpr size_t inlined_size = 4; + using Alloc = CountingAllocator; + using AllocFxdArr = absl::FixedArray; + + int64_t allocated = 0; + int64_t active_instances = 0; + + { + const int ia[] = {0, 1, 2, 3, 4, 5, 6, 7}; + Alloc alloc(&allocated, &active_instances); + + AllocFxdArr arr(ia, ia + ABSL_ARRAYSIZE(ia), alloc); + + EXPECT_EQ(allocated, arr.size() * sizeof(int)); + static_cast(arr); + } + + EXPECT_EQ(active_instances, 0); +} + +TEST(AllocatorSupportTest, CountCopyInlineAllocations) { + constexpr size_t inlined_size = 4; + using Alloc = CountingAllocator; + using AllocFxdArr = absl::FixedArray; + + int64_t allocated1 = 0; + int64_t allocated2 = 0; + int64_t active_instances = 0; + Alloc alloc(&allocated1, &active_instances); + Alloc alloc2(&allocated2, &active_instances); + + { + int initial_value = 1; + + AllocFxdArr arr1(inlined_size / 2, initial_value, alloc); + + EXPECT_EQ(allocated1, 0); + + AllocFxdArr arr2(arr1, alloc2); + + EXPECT_EQ(allocated2, 0); + static_cast(arr1); + static_cast(arr2); + } + + EXPECT_EQ(active_instances, 0); +} + +TEST(AllocatorSupportTest, CountCopyOutoflineAllocations) { + constexpr size_t inlined_size = 4; + using Alloc = CountingAllocator; + using AllocFxdArr = absl::FixedArray; + + int64_t allocated1 = 0; + int64_t allocated2 = 0; + int64_t active_instances = 0; + Alloc alloc(&allocated1, &active_instances); + Alloc alloc2(&allocated2, &active_instances); + + { + int initial_value = 1; + + AllocFxdArr arr1(inlined_size * 2, initial_value, alloc); + + EXPECT_EQ(allocated1, arr1.size() * sizeof(int)); + + AllocFxdArr arr2(arr1, alloc2); + + EXPECT_EQ(allocated2, inlined_size * 2 * sizeof(int)); + static_cast(arr1); + static_cast(arr2); + } + + EXPECT_EQ(active_instances, 0); +} + +TEST(AllocatorSupportTest, SizeValAllocConstructor) { + using testing::AllOf; + using testing::Each; + using testing::SizeIs; + + constexpr size_t inlined_size = 4; + using Alloc = CountingAllocator; + using AllocFxdArr = absl::FixedArray; + + { + auto len = inlined_size / 2; + auto val = 0; + int64_t allocated = 0; + AllocFxdArr arr(len, val, Alloc(&allocated)); + + EXPECT_EQ(allocated, 0); + EXPECT_THAT(arr, AllOf(SizeIs(len), Each(0))); + } + + { + auto len = inlined_size * 2; + auto val = 0; + int64_t allocated = 0; + AllocFxdArr arr(len, val, Alloc(&allocated)); + + EXPECT_EQ(allocated, len * sizeof(int)); + EXPECT_THAT(arr, AllOf(SizeIs(len), Each(0))); + } +} + #ifdef ADDRESS_SANITIZER TEST(FixedArrayTest, AddressSanitizerAnnotations1) { absl::FixedArray a(10); @@ -655,5 +867,4 @@ TEST(FixedArrayTest, AddressSanitizerAnnotations4) { EXPECT_DEATH(raw[21] = ThreeInts(), "container-overflow"); } #endif // ADDRESS_SANITIZER - } // namespace diff --git a/absl/container/internal/compressed_tuple.h b/absl/container/internal/compressed_tuple.h new file mode 100644 index 00000000..cc52614f --- /dev/null +++ b/absl/container/internal/compressed_tuple.h @@ -0,0 +1,175 @@ +// Copyright 2018 The Abseil Authors. +// +// Licensed 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. +// +// Helper class to perform the Empty Base Optimization. +// Ts can contain classes and non-classes, empty or not. For the ones that +// are empty classes, we perform the optimization. If all types in Ts are empty +// classes, then CompressedTuple is itself an empty class. +// +// To access the members, use member get() function. +// +// Eg: +// absl::container_internal::CompressedTuple value(7, t1, t2, +// t3); +// assert(value.get<0>() == 7); +// T1& t1 = value.get<1>(); +// const T2& t2 = value.get<2>(); +// ... +// +// http://en.cppreference.com/w/cpp/language/ebo + +#ifndef ABSL_CONTAINER_INTERNAL_COMPRESSED_TUPLE_H_ +#define ABSL_CONTAINER_INTERNAL_COMPRESSED_TUPLE_H_ + +#include +#include +#include + +#include "absl/utility/utility.h" + +#ifdef _MSC_VER +// We need to mark these classes with this declspec to ensure that +// CompressedTuple happens. +#define ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC __declspec(empty_bases) +#else // _MSC_VER +#define ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC +#endif // _MSC_VER + +namespace absl { +namespace container_internal { + +template +class CompressedTuple; + +namespace internal_compressed_tuple { + +template +struct Elem; +template +struct Elem, I> + : std::tuple_element> {}; +template +using ElemT = typename Elem::type; + +// Use the __is_final intrinsic if available. Where it's not available, classes +// declared with the 'final' specifier cannot be used as CompressedTuple +// elements. +// TODO(sbenza): Replace this with std::is_final in C++14. +template +constexpr bool IsFinal() { +#if defined(__clang__) || defined(__GNUC__) + return __is_final(T); +#else + return false; +#endif +} + +template +constexpr bool ShouldUseBase() { + return std::is_class::value && std::is_empty::value && !IsFinal(); +} + +// The storage class provides two specializations: +// - For empty classes, it stores T as a base class. +// - For everything else, it stores T as a member. +template >()> +struct Storage { + using T = ElemT; + T value; + constexpr Storage() = default; + explicit constexpr Storage(T&& v) : value(absl::forward(v)) {} + constexpr const T& get() const { return value; } + T& get() { return value; } +}; + +template +struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC Storage + : ElemT { + using T = internal_compressed_tuple::ElemT; + constexpr Storage() = default; + explicit constexpr Storage(T&& v) : T(absl::forward(v)) {} + constexpr const T& get() const { return *this; } + T& get() { return *this; } +}; + +template +struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTupleImpl; + +template +struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC + CompressedTupleImpl, absl::index_sequence> + // We use the dummy identity function through std::integral_constant to + // convince MSVC of accepting and expanding I in that context. Without it + // you would get: + // error C3548: 'I': parameter pack cannot be used in this context + : Storage, + std::integral_constant::value>... { + constexpr CompressedTupleImpl() = default; + explicit constexpr CompressedTupleImpl(Ts&&... args) + : Storage, I>(absl::forward(args))... {} +}; + +} // namespace internal_compressed_tuple + +// Helper class to perform the Empty Base Class Optimization. +// Ts can contain classes and non-classes, empty or not. For the ones that +// are empty classes, we perform the CompressedTuple. If all types in Ts are +// empty classes, then CompressedTuple is itself an empty class. +// +// To access the members, use member .get() function. +// +// Eg: +// absl::container_internal::CompressedTuple value(7, t1, t2, +// t3); +// assert(value.get<0>() == 7); +// T1& t1 = value.get<1>(); +// const T2& t2 = value.get<2>(); +// ... +// +// http://en.cppreference.com/w/cpp/language/ebo +template +class ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTuple + : private internal_compressed_tuple::CompressedTupleImpl< + CompressedTuple, absl::index_sequence_for> { + private: + template + using ElemT = internal_compressed_tuple::ElemT; + + public: + constexpr CompressedTuple() = default; + explicit constexpr CompressedTuple(Ts... base) + : CompressedTuple::CompressedTupleImpl(absl::forward(base)...) {} + + template + ElemT& get() { + return internal_compressed_tuple::Storage::get(); + } + + template + constexpr const ElemT& get() const { + return internal_compressed_tuple::Storage::get(); + } +}; + +// Explicit specialization for a zero-element tuple +// (needed to avoid ambiguous overloads for the default constructor). +template <> +class ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTuple<> {}; + +} // namespace container_internal +} // namespace absl + +#undef ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC + +#endif // ABSL_CONTAINER_INTERNAL_COMPRESSED_TUPLE_H_ diff --git a/absl/container/internal/compressed_tuple_test.cc b/absl/container/internal/compressed_tuple_test.cc new file mode 100644 index 00000000..45030c67 --- /dev/null +++ b/absl/container/internal/compressed_tuple_test.cc @@ -0,0 +1,166 @@ +// Copyright 2018 The Abseil Authors. +// +// Licensed 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 "absl/container/internal/compressed_tuple.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace absl { +namespace container_internal { +namespace { + +template +struct Empty {}; + +template +struct NotEmpty { + T value; +}; + +template +struct TwoValues { + T value1; + U value2; +}; + +TEST(CompressedTupleTest, Sizeof) { + EXPECT_EQ(sizeof(int), sizeof(CompressedTuple)); + EXPECT_EQ(sizeof(int), sizeof(CompressedTuple>)); + EXPECT_EQ(sizeof(int), sizeof(CompressedTuple, Empty<1>>)); + EXPECT_EQ(sizeof(int), + sizeof(CompressedTuple, Empty<1>, Empty<2>>)); + + EXPECT_EQ(sizeof(TwoValues), + sizeof(CompressedTuple>)); + EXPECT_EQ(sizeof(TwoValues), + sizeof(CompressedTuple, NotEmpty>)); + EXPECT_EQ(sizeof(TwoValues), + sizeof(CompressedTuple, NotEmpty, Empty<1>>)); +} + +TEST(CompressedTupleTest, Access) { + struct S { + std::string x; + }; + CompressedTuple, S> x(7, {}, S{"ABC"}); + EXPECT_EQ(sizeof(x), sizeof(TwoValues)); + EXPECT_EQ(7, x.get<0>()); + EXPECT_EQ("ABC", x.get<2>().x); +} + +TEST(CompressedTupleTest, NonClasses) { + CompressedTuple x(7, "ABC"); + EXPECT_EQ(7, x.get<0>()); + EXPECT_STREQ("ABC", x.get<1>()); +} + +TEST(CompressedTupleTest, MixClassAndNonClass) { + CompressedTuple, NotEmpty> x(7, "ABC", {}, + {1.25}); + struct Mock { + int v; + const char* p; + double d; + }; + EXPECT_EQ(sizeof(x), sizeof(Mock)); + EXPECT_EQ(7, x.get<0>()); + EXPECT_STREQ("ABC", x.get<1>()); + EXPECT_EQ(1.25, x.get<3>().value); +} + +TEST(CompressedTupleTest, Nested) { + CompressedTuple, + CompressedTuple>> + x(1, CompressedTuple(2), + CompressedTuple>(3, CompressedTuple(4))); + EXPECT_EQ(1, x.get<0>()); + EXPECT_EQ(2, x.get<1>().get<0>()); + EXPECT_EQ(3, x.get<2>().get<0>()); + EXPECT_EQ(4, x.get<2>().get<1>().get<0>()); + + CompressedTuple, Empty<0>, + CompressedTuple, CompressedTuple>>> + y; + std::set*> empties{&y.get<0>(), &y.get<1>(), &y.get<2>().get<0>(), + &y.get<2>().get<1>().get<0>()}; +#ifdef _MSC_VER + // MSVC has a bug where many instances of the same base class are layed out in + // the same address when using __declspec(empty_bases). + // This will be fixed in a future version of MSVC. + int expected = 1; +#else + int expected = 4; +#endif + EXPECT_EQ(expected, sizeof(y)); + EXPECT_EQ(expected, empties.size()); + EXPECT_EQ(sizeof(y), sizeof(Empty<0>) * empties.size()); + + EXPECT_EQ(4 * sizeof(char), + sizeof(CompressedTuple, + CompressedTuple>)); + EXPECT_TRUE( + (std::is_empty>, + CompressedTuple>>>::value)); +} + +TEST(CompressedTupleTest, Reference) { + int i = 7; + std::string s = "Very long std::string that goes in the heap"; + CompressedTuple x(i, i, s, s); + + // Sanity check. We should have not moved from `s` + EXPECT_EQ(s, "Very long std::string that goes in the heap"); + + EXPECT_EQ(x.get<0>(), x.get<1>()); + EXPECT_NE(&x.get<0>(), &x.get<1>()); + EXPECT_EQ(&x.get<1>(), &i); + + EXPECT_EQ(x.get<2>(), x.get<3>()); + EXPECT_NE(&x.get<2>(), &x.get<3>()); + EXPECT_EQ(&x.get<3>(), &s); +} + +TEST(CompressedTupleTest, NoElements) { + CompressedTuple<> x; + static_cast(x); // Silence -Wunused-variable. + EXPECT_TRUE(std::is_empty>::value); +} + +TEST(CompressedTupleTest, Constexpr) { + constexpr CompressedTuple> x( + 7, 1.25, CompressedTuple(5)); + constexpr int x0 = x.get<0>(); + constexpr double x1 = x.get<1>(); + constexpr int x2 = x.get<2>().get<0>(); + EXPECT_EQ(x0, 7); + EXPECT_EQ(x1, 1.25); + EXPECT_EQ(x2, 5); +} + +#if defined(__clang__) || defined(__GNUC__) +TEST(CompressedTupleTest, EmptyFinalClass) { + struct S final { + int f() const { return 5; } + }; + CompressedTuple x; + EXPECT_EQ(x.get<0>().f(), 5); +} +#endif + +} // namespace +} // namespace container_internal +} // namespace absl diff --git a/absl/memory/memory.h b/absl/memory/memory.h index f207169a..c7caf8b9 100644 --- a/absl/memory/memory.h +++ b/absl/memory/memory.h @@ -641,38 +641,56 @@ struct default_allocator_is_nothrow : std::false_type {}; #endif namespace memory_internal { -// TODO(b110200014): Implement proper backports -template -void DefaultConstruct(ForwardIt it) { - using value_type = typename std::iterator_traits::value_type; - ::new (static_cast(std::addressof(*it))) value_type; -} // namespace memory_internal - #ifdef ABSL_HAVE_EXCEPTIONS -template -void uninitialized_default_construct_n(ForwardIt first, Size size) { - for (ForwardIt cur = first; size > 0; static_cast(++cur), --size) { +template +void ConstructStorage(Allocator* alloc, StorageElement* first, + StorageElement* last, const Args&... args) { + for (StorageElement* cur = first; cur != last; ++cur) { + try { + std::allocator_traits::construct(*alloc, cur, args...); + } catch (...) { + while (cur != first) { + --cur; + std::allocator_traits::destroy(*alloc, cur); + } + throw; + } + } +} +template +void CopyToStorageFromRange(Allocator* alloc, StorageElement* destination, + Iterator first, Iterator last) { + for (StorageElement* cur = destination; first != last; + static_cast(++cur), static_cast(++first)) { try { - absl::memory_internal::DefaultConstruct(cur); + std::allocator_traits::construct(*alloc, cur, *first); } catch (...) { - using value_type = typename std::iterator_traits::value_type; - for (; first != cur; ++first) { - first->~value_type(); + while (cur != destination) { + --cur; + std::allocator_traits::destroy(*alloc, cur); } throw; } } } #else // ABSL_HAVE_EXCEPTIONS -template -void uninitialized_default_construct_n(ForwardIt first, Size size) { - for (; size > 0; static_cast(++first), --size) { - absl::memory_internal::DefaultConstruct(first); +template +void ConstructStorage(Allocator* alloc, StorageElement* first, + StorageElement* last, const Args&... args) { + for (; first != last; ++first) { + std::allocator_traits::construct(*alloc, first, args...); + } +} +template +void CopyToStorageFromRange(Allocator* alloc, StorageElement* destination, + Iterator first, Iterator last) { + for (; first != last; + static_cast(++destination), static_cast(++first)) { + std::allocator_traits::construct(*alloc, destination, *first); } } #endif // ABSL_HAVE_EXCEPTIONS } // namespace memory_internal - } // namespace absl #endif // ABSL_MEMORY_MEMORY_H_ diff --git a/absl/memory/memory_exception_safety_test.cc b/absl/memory/memory_exception_safety_test.cc index fb8b561d..d1f6e84f 100644 --- a/absl/memory/memory_exception_safety_test.cc +++ b/absl/memory/memory_exception_safety_test.cc @@ -48,16 +48,5 @@ TEST(MakeUnique, CheckForLeaks) { })); } -TEST(MemoryInternal, UninitDefaultConstructNNonTrivial) { - EXPECT_TRUE(testing::MakeExceptionSafetyTester() - .WithInitialValue(ThrowerList{}) - .WithOperation([&](ThrowerList* list_ptr) { - absl::memory_internal::uninitialized_default_construct_n( - list_ptr->data(), kLength); - }) - .WithInvariants([&](...) { return true; }) - .Test()); -} - } // namespace } // namespace absl diff --git a/absl/memory/memory_test.cc b/absl/memory/memory_test.cc index 8ff1945d..dee9b486 100644 --- a/absl/memory/memory_test.cc +++ b/absl/memory/memory_test.cc @@ -611,47 +611,4 @@ TEST(AllocatorNoThrowTest, CustomAllocator) { EXPECT_FALSE(absl::allocator_is_nothrow::value); } -TEST(MemoryInternal, UninitDefaultConstructNTrivial) { - constexpr int kInitialValue = 123; - constexpr int kExpectedValue = kInitialValue; // Expect no-op behavior - constexpr int len = 5; - - struct TestObj { - int val; - }; - static_assert(absl::is_trivially_default_constructible::value, ""); - static_assert(absl::is_trivially_destructible::value, ""); - - TestObj objs[len]; - for (auto& obj : objs) { - obj.val = kInitialValue; - } - - absl::memory_internal::uninitialized_default_construct_n(objs, len); - for (auto& obj : objs) { - EXPECT_EQ(obj.val, kExpectedValue); - } -} - -TEST(MemoryInternal, UninitDefaultConstructNNonTrivial) { - constexpr int kInitialValue = 123; - constexpr int kExpectedValue = 0; // Expect value-construction behavior - constexpr int len = 5; - - struct TestObj { - int val{kExpectedValue}; - }; - static_assert(absl::is_trivially_destructible::value, ""); - - TestObj objs[len]; - for (auto& obj : objs) { - obj.val = kInitialValue; - } - - absl::memory_internal::uninitialized_default_construct_n(objs, len); - for (auto& obj : objs) { - EXPECT_EQ(obj.val, kExpectedValue); - } -} - } // namespace diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h index 8d3264f1..457b8908 100644 --- a/absl/meta/type_traits.h +++ b/absl/meta/type_traits.h @@ -44,6 +44,7 @@ namespace absl { namespace type_traits_internal { + template struct VoidTImpl { using type = void; @@ -61,6 +62,49 @@ struct default_alignment_of_aligned_storage` is +// evaluated as soon as the type `is_detected` undergoes +// substitution, regardless of whether or not the `::value` is accessed. That +// is inconsistent with all other standard traits and prevents lazy evaluation +// in larger contexts (such as if the `is_detected` check is a trailing argument +// of a `conjunction`. This implementation opts to instead be lazy in the same +// way that the standard traits are (this "defect" of the detection idiom +// specifications has been reported). + +template class Op, class... Args> +struct is_detected_impl { + using type = std::false_type; +}; + +template