summaryrefslogtreecommitdiff
path: root/absl/base
diff options
context:
space:
mode:
Diffstat (limited to 'absl/base')
-rw-r--r--absl/base/BUILD.bazel37
-rw-r--r--absl/base/CMakeLists.txt25
-rw-r--r--absl/base/no_destructor.h217
-rw-r--r--absl/base/no_destructor_benchmark.cc165
-rw-r--r--absl/base/no_destructor_test.cc209
5 files changed, 653 insertions, 0 deletions
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel
index daa10af6..60d05a83 100644
--- a/absl/base/BUILD.bazel
+++ b/absl/base/BUILD.bazel
@@ -70,6 +70,14 @@ cc_library(
)
cc_library(
+ name = "no_destructor",
+ hdrs = ["no_destructor.h"],
+ copts = ABSL_DEFAULT_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [":config"],
+)
+
+cc_library(
name = "nullability",
srcs = ["internal/nullability_impl.h"],
hdrs = ["nullability.h"],
@@ -584,6 +592,35 @@ cc_test(
)
cc_test(
+ name = "no_destructor_test",
+ srcs = ["no_destructor_test.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ deps = [
+ ":config",
+ ":no_destructor",
+ ":raw_logging_internal",
+ "@com_google_googletest//:gtest",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+cc_binary(
+ name = "no_destructor_benchmark",
+ testonly = 1,
+ srcs = ["no_destructor_benchmark.cc"],
+ copts = ABSL_TEST_COPTS,
+ linkopts = ABSL_DEFAULT_LINKOPTS,
+ tags = ["benchmark"],
+ visibility = ["//visibility:private"],
+ deps = [
+ ":no_destructor",
+ ":raw_logging_internal",
+ "@com_github_google_benchmark//:benchmark_main",
+ ],
+)
+
+cc_test(
name = "nullability_test",
srcs = ["nullability_test.cc"],
deps = [
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt
index 3f4a1c42..9ca5cf8b 100644
--- a/absl/base/CMakeLists.txt
+++ b/absl/base/CMakeLists.txt
@@ -57,6 +57,17 @@ absl_cc_library(
absl_cc_library(
NAME
+ no_destructor
+ HDRS
+ "no_destructor.h"
+ DEPS
+ absl::config
+ COPTS
+ ${ABSL_DEFAULT_COPTS}
+)
+
+absl_cc_library(
+ NAME
nullability
HDRS
"nullability.h"
@@ -510,6 +521,20 @@ absl_cc_test(
absl_cc_test(
NAME
+ no_destructor_test
+ SRCS
+ "no_destructor_test.cc"
+ COPTS
+ ${ABSL_TEST_COPTS}
+ DEPS
+ absl::no_destructor
+ absl::config
+ absl::raw_logging_internal
+ GTest::gtest_main
+)
+
+absl_cc_test(
+ NAME
raw_logging_test
SRCS
"raw_logging_test.cc"
diff --git a/absl/base/no_destructor.h b/absl/base/no_destructor.h
new file mode 100644
index 00000000..d4b16a6e
--- /dev/null
+++ b/absl/base/no_destructor.h
@@ -0,0 +1,217 @@
+// Copyright 2023 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
+//
+// https://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.
+//
+// -----------------------------------------------------------------------------
+// File: no_destructor.h
+// -----------------------------------------------------------------------------
+//
+// This header file defines the absl::NoDestructor<T> wrapper for defining a
+// static type that does not need to be destructed upon program exit. Instead,
+// such an object survives during program exit (and can be safely accessed at
+// any time).
+//
+// Objects of such type, if constructed safely and under the right conditions,
+// provide two main benefits over other alternatives:
+//
+// * Global objects not normally allowed due to concerns of destruction order
+// (i.e. no "complex globals") can be safely allowed, provided that such
+// objects can be constant initialized.
+// * Function scope static objects can be optimized to avoid heap allocation,
+// pointer chasing, and allow lazy construction.
+//
+// See below for complete details.
+
+
+#ifndef ABSL_BASE_NO_DESTRUCTOR_H_
+#define ABSL_BASE_NO_DESTRUCTOR_H_
+
+#include <new>
+#include <type_traits>
+#include <utility>
+
+#include "absl/base/config.h"
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+
+// absl::NoDestructor<T>
+//
+// NoDestructor<T> is a wrapper around an object of type T that behaves as an
+// object of type T but never calls T's destructor. NoDestructor<T> makes it
+// safer and/or more efficient to use such objects in static storage contexts:
+// as global or function scope static variables.
+//
+// An instance of absl::NoDestructor<T> has similar type semantics to an
+// instance of T:
+//
+// * Constructs in the same manner as an object of type T through perfect
+// forwarding.
+// * Provides pointer/reference semantic access to the object of type T via
+// `->`, `*`, and `get()`.
+// (Note that `const NoDestructor<T>` works like a pointer to const `T`.)
+//
+// An object of type NoDestructor<T> should be defined in static storage:
+// as either a global static object, or as a function scope static variable.
+//
+// Additionally, NoDestructor<T> provides the following benefits:
+//
+// * Never calls T's destructor for the object
+// * If the object is a function-local static variable, the type can be
+// lazily constructed.
+//
+// An object of type NoDestructor<T> is "trivially destructible" in the notion
+// that its destructor is never run. Provided that an object of this type can be
+// safely initialized and does not need to be cleaned up on program shutdown,
+// NoDestructor<T> allows you to define global static variables, since Google's
+// C++ style guide ban on such objects doesn't apply to objects that are
+// trivially destructible.
+//
+// Usage as Global Static Variables
+//
+// NoDestructor<T> allows declaration of a global object with a non-trivial
+// constructor in static storage without needing to add a destructor.
+// However, such objects still need to worry about initialization order, so
+// such objects should be const initialized:
+//
+// // Global or namespace scope.
+// ABSL_CONST_INIT absl::NoDestructor<MyRegistry> reg{"foo", "bar", 8008};
+//
+// Note that if your object already has a trivial destructor, you don't need to
+// use NoDestructor<T>.
+//
+// Usage as Function Scope Static Variables
+//
+// Function static objects will be lazily initialized within static storage:
+//
+// // Function scope.
+// const std::string& MyString() {
+// static const absl::NoDestructor<std::string> x("foo");
+// return *x;
+// }
+//
+// For function static variables, NoDestructor avoids heap allocation and can be
+// inlined in static storage, resulting in exactly-once, thread-safe
+// construction of an object, and very fast access thereafter (the cost is a few
+// extra cycles).
+//
+// Using NoDestructor<T> in this manner is generally better than other patterns
+// which require pointer chasing:
+//
+// // Prefer using absl::NoDestructor<T> instead for the static variable.
+// const std::string& MyString() {
+// static const std::string* x = new std::string("foo");
+// return *x;
+// }
+//
+template <typename T>
+class NoDestructor {
+ public:
+ // Forwards arguments to the T's constructor: calls T(args...).
+ template <typename... Ts,
+ // Disable this overload when it might collide with copy/move.
+ typename std::enable_if<!std::is_same<void(std::decay_t<Ts>&...),
+ void(NoDestructor&)>::value,
+ int>::type = 0>
+ explicit constexpr NoDestructor(Ts&&... args)
+ : impl_(std::forward<Ts>(args)...) {}
+
+ // Forwards copy and move construction for T. Enables usage like this:
+ // static NoDestructor<std::array<string, 3>> x{{{"1", "2", "3"}}};
+ // static NoDestructor<std::vector<int>> x{{1, 2, 3}};
+ explicit constexpr NoDestructor(const T& x) : impl_(x) {}
+ explicit constexpr NoDestructor(T&& x)
+ : impl_(std::move(x)) {}
+
+ // No copying.
+ NoDestructor(const NoDestructor&) = delete;
+ NoDestructor& operator=(const NoDestructor&) = delete;
+
+ // Pretend to be a smart pointer to T with deep constness.
+ // Never returns a null pointer.
+ T& operator*() { return *get(); }
+ T* operator->() { return get(); }
+ T* get() { return impl_.get(); }
+ const T& operator*() const { return *get(); }
+ const T* operator->() const { return get(); }
+ const T* get() const { return impl_.get(); }
+
+ private:
+ class DirectImpl {
+ public:
+ template <typename... Args>
+ explicit constexpr DirectImpl(Args&&... args)
+ : value_(std::forward<Args>(args)...) {}
+ const T* get() const { return &value_; }
+ T* get() { return &value_; }
+
+ private:
+ T value_;
+ };
+
+ class PlacementImpl {
+ public:
+ template <typename... Args>
+ explicit PlacementImpl(Args&&... args) {
+ new (&space_) T(std::forward<Args>(args)...);
+ }
+ const T* get() const {
+ return Launder(reinterpret_cast<const T*>(&space_));
+ }
+ T* get() { return Launder(reinterpret_cast<T*>(&space_)); }
+
+ private:
+ template <typename P>
+ static P* Launder(P* p) {
+#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L
+ return std::launder(p);
+#elif ABSL_HAVE_BUILTIN(__builtin_launder)
+ return __builtin_launder(p);
+#else
+ // When `std::launder` or equivalent are not available, we rely on
+ // undefined behavior, which works as intended on Abseil's officially
+ // supported platforms as of Q3 2023.
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#endif
+ return p;
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+#endif
+ }
+
+ alignas(T) unsigned char space_[sizeof(T)];
+ };
+
+ // If the object is trivially destructible we use a member directly to avoid
+ // potential once-init runtime initialization. It somewhat defeats the
+ // purpose of NoDestructor in this case, but this makes the class more
+ // friendly to generic code.
+ std::conditional_t<std::is_trivially_destructible<T>::value, DirectImpl,
+ PlacementImpl>
+ impl_;
+};
+
+#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+// Provide 'Class Template Argument Deduction': the type of NoDestructor's T
+// will be the same type as the argument passed to NoDestructor's constructor.
+template <typename T>
+NoDestructor(T) -> NoDestructor<T>;
+#endif // ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // ABSL_BASE_NO_DESTRUCTOR_H_
diff --git a/absl/base/no_destructor_benchmark.cc b/absl/base/no_destructor_benchmark.cc
new file mode 100644
index 00000000..5fc88f1d
--- /dev/null
+++ b/absl/base/no_destructor_benchmark.cc
@@ -0,0 +1,165 @@
+// Copyright 2023 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
+//
+// https://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 <cstdint>
+
+#include "absl/base/internal/raw_logging.h"
+#include "absl/base/no_destructor.h"
+#include "benchmark/benchmark.h"
+
+namespace {
+
+// Number of static-NoDestructor-in-a-function to exercise.
+// This must be low enough not to hit template instantiation limits
+// (happens around 1000).
+constexpr int kNumObjects = 1; // set to 512 when doing benchmarks
+ // 1 is faster to compile: just one templated
+ // function instantiation
+
+// Size of individual objects to benchmark static-NoDestructor-in-a-function
+// usage with.
+constexpr int kObjSize = sizeof(void*)*1;
+
+// Simple object of kObjSize bytes (rounded to int).
+// We benchmark complete reading of its state via Verify().
+class BM_Blob {
+ public:
+ BM_Blob(int val) { for (auto& d : data_) d = val; }
+ BM_Blob() : BM_Blob(-1) {}
+ void Verify(int val) const { // val must be the c-tor argument
+ for (auto& d : data_) ABSL_INTERNAL_CHECK(d == val, "");
+ }
+ private:
+ int data_[kObjSize / sizeof(int) > 0 ? kObjSize / sizeof(int) : 1];
+};
+
+// static-NoDestructor-in-a-function pattern instances.
+// We'll instantiate kNumObjects of them.
+template<int i>
+const BM_Blob& NoDestrBlobFunc() {
+ static absl::NoDestructor<BM_Blob> x(i);
+ return *x;
+}
+
+// static-heap-ptr-in-a-function pattern instances
+// We'll instantiate kNumObjects of them.
+template<int i>
+const BM_Blob& OnHeapBlobFunc() {
+ static BM_Blob* x = new BM_Blob(i);
+ return *x;
+}
+
+// Type for NoDestrBlobFunc or OnHeapBlobFunc.
+typedef const BM_Blob& (*FuncType)();
+
+// ========================================================================= //
+// Simple benchmarks that read a single BM_Blob over and over, hence
+// all they touch fits into L1 CPU cache:
+
+// Direct non-POD global variable (style guide violation) as a baseline.
+static BM_Blob direct_blob(0);
+
+void BM_Direct(benchmark::State& state) {
+ for (auto s : state) {
+ direct_blob.Verify(0);
+ }
+}
+BENCHMARK(BM_Direct);
+
+void BM_NoDestr(benchmark::State& state) {
+ for (auto s : state) {
+ NoDestrBlobFunc<0>().Verify(0);
+ }
+}
+BENCHMARK(BM_NoDestr);
+
+void BM_OnHeap(benchmark::State& state) {
+ for (auto s : state) {
+ OnHeapBlobFunc<0>().Verify(0);
+ }
+}
+BENCHMARK(BM_OnHeap);
+
+// ========================================================================= //
+// Benchmarks that read kNumObjects of BM_Blob over and over, hence with
+// appropriate values of sizeof(BM_Blob) and kNumObjects their working set
+// can exceed a given layer of CPU cache.
+
+// Type of benchmark to select between NoDestrBlobFunc and OnHeapBlobFunc.
+enum BM_Type { kNoDestr, kOnHeap, kDirect };
+
+// BlobFunc<n>(t, i) returns the i-th function of type t.
+// n must be larger than i (we'll use kNumObjects for n).
+template<int n>
+FuncType BlobFunc(BM_Type t, int i) {
+ if (i == n) {
+ switch (t) {
+ case kNoDestr: return &NoDestrBlobFunc<n>;
+ case kOnHeap: return &OnHeapBlobFunc<n>;
+ case kDirect: return nullptr;
+ }
+ }
+ return BlobFunc<n-1>(t, i);
+}
+
+template<>
+FuncType BlobFunc<0>(BM_Type t, int i) {
+ ABSL_INTERNAL_CHECK(i == 0, "");
+ switch (t) {
+ case kNoDestr: return &NoDestrBlobFunc<0>;
+ case kOnHeap: return &OnHeapBlobFunc<0>;
+ case kDirect: return nullptr;
+ }
+ return nullptr;
+}
+
+// Direct non-POD global variables (style guide violation) as a baseline.
+static BM_Blob direct_blobs[kNumObjects];
+
+// Helper that cheaply maps benchmark iteration to randomish index in
+// [0, kNumObjects).
+int RandIdx(int i) {
+ // int64 is to avoid overflow and generating negative return values:
+ return (static_cast<int64_t>(i) * 13) % kNumObjects;
+}
+
+// Generic benchmark working with kNumObjects for any of the possible BM_Type.
+template <BM_Type t>
+void BM_Many(benchmark::State& state) {
+ FuncType funcs[kNumObjects];
+ for (int i = 0; i < kNumObjects; ++i) {
+ funcs[i] = BlobFunc<kNumObjects-1>(t, i);
+ }
+ if (t == kDirect) {
+ for (auto s : state) {
+ int idx = RandIdx(state.iterations());
+ direct_blobs[idx].Verify(-1);
+ }
+ } else {
+ for (auto s : state) {
+ int idx = RandIdx(state.iterations());
+ funcs[idx]().Verify(idx);
+ }
+ }
+}
+
+void BM_DirectMany(benchmark::State& state) { BM_Many<kDirect>(state); }
+void BM_NoDestrMany(benchmark::State& state) { BM_Many<kNoDestr>(state); }
+void BM_OnHeapMany(benchmark::State& state) { BM_Many<kOnHeap>(state); }
+
+BENCHMARK(BM_DirectMany);
+BENCHMARK(BM_NoDestrMany);
+BENCHMARK(BM_OnHeapMany);
+
+} // namespace
diff --git a/absl/base/no_destructor_test.cc b/absl/base/no_destructor_test.cc
new file mode 100644
index 00000000..71693c7e
--- /dev/null
+++ b/absl/base/no_destructor_test.cc
@@ -0,0 +1,209 @@
+// Copyright 2023 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
+//
+// https://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/base/no_destructor.h"
+
+#include <array>
+#include <initializer_list>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/config.h"
+#include "absl/base/internal/raw_logging.h"
+
+namespace {
+
+struct Blob {
+ Blob() : val(42) {}
+ Blob(int x, int y) : val(x + y) {}
+ Blob(std::initializer_list<int> xs) {
+ val = 0;
+ for (auto& x : xs) val += x;
+ }
+
+ Blob(const Blob& /*b*/) = delete;
+ Blob(Blob&& b) noexcept : val(b.val) {
+ b.moved_out = true;
+ } // moving is fine
+
+ // no crash: NoDestructor indeed does not destruct (the moved-out Blob
+ // temporaries do get destroyed though)
+ ~Blob() { ABSL_INTERNAL_CHECK(moved_out, "~Blob"); }
+
+ int val;
+ bool moved_out = false;
+};
+
+struct TypeWithDeletedDestructor {
+ ~TypeWithDeletedDestructor() = delete;
+};
+
+TEST(NoDestructorTest, DestructorNeverCalled) {
+ absl::NoDestructor<TypeWithDeletedDestructor> a;
+ (void)a;
+}
+
+TEST(NoDestructorTest, Noncopyable) {
+ using T = absl::NoDestructor<int>;
+
+ EXPECT_FALSE((std::is_constructible<T, T>::value));
+ EXPECT_FALSE((std::is_constructible<T, const T>::value));
+ EXPECT_FALSE((std::is_constructible<T, T&>::value));
+ EXPECT_FALSE((std::is_constructible<T, const T&>::value));
+
+ EXPECT_FALSE((std::is_assignable<T&, T>::value));
+ EXPECT_FALSE((std::is_assignable<T&, const T>::value));
+ EXPECT_FALSE((std::is_assignable<T&, T&>::value));
+ EXPECT_FALSE((std::is_assignable<T&, const T&>::value));
+}
+
+TEST(NoDestructorTest, Interface) {
+ EXPECT_TRUE(std::is_trivially_destructible<absl::NoDestructor<Blob>>::value);
+ EXPECT_TRUE(
+ std::is_trivially_destructible<absl::NoDestructor<const Blob>>::value);
+ {
+ absl::NoDestructor<Blob> b; // default c-tor
+ // access: *, ->, get()
+ EXPECT_EQ(42, (*b).val);
+ (*b).val = 55;
+ EXPECT_EQ(55, b->val);
+ b->val = 66;
+ EXPECT_EQ(66, b.get()->val);
+ b.get()->val = 42; // NOLINT
+ EXPECT_EQ(42, (*b).val);
+ }
+ {
+ absl::NoDestructor<const Blob> b(70, 7); // regular c-tor, const
+ EXPECT_EQ(77, (*b).val);
+ EXPECT_EQ(77, b->val);
+ EXPECT_EQ(77, b.get()->val);
+ }
+ {
+ const absl::NoDestructor<Blob> b{
+ {20, 28, 40}}; // init-list c-tor, deep const
+ // This only works in clang, not in gcc:
+ // const absl::NoDestructor<Blob> b({20, 28, 40});
+ EXPECT_EQ(88, (*b).val);
+ EXPECT_EQ(88, b->val);
+ EXPECT_EQ(88, b.get()->val);
+ }
+}
+
+TEST(NoDestructorTest, SfinaeRegressionAbstractArg) {
+ struct Abstract {
+ virtual ~Abstract() = default;
+ virtual int foo() const = 0;
+ };
+
+ struct Concrete : Abstract {
+ int foo() const override { return 17; }
+ };
+
+ struct UsesAbstractInConstructor {
+ explicit UsesAbstractInConstructor(const Abstract& abstract)
+ : i(abstract.foo()) {}
+ int i;
+ };
+
+ Concrete input;
+ absl::NoDestructor<UsesAbstractInConstructor> foo1(input);
+ EXPECT_EQ(foo1->i, 17);
+ absl::NoDestructor<UsesAbstractInConstructor> foo2(
+ static_cast<const Abstract&>(input));
+ EXPECT_EQ(foo2->i, 17);
+}
+
+// ========================================================================= //
+
+std::string* Str0() {
+ static absl::NoDestructor<std::string> x;
+ return x.get();
+}
+
+extern const std::string& Str2();
+
+const char* Str1() {
+ static absl::NoDestructor<std::string> x(Str2() + "_Str1");
+ return x->c_str();
+}
+
+const std::string& Str2() {
+ static absl::NoDestructor<std::string> x("Str2");
+ return *x;
+}
+
+const std::string& Str2Copy() {
+ // Exercise copy construction
+ static absl::NoDestructor<std::string> x(Str2());
+ return *x;
+}
+
+typedef std::array<std::string, 3> MyArray;
+const MyArray& Array() {
+ static absl::NoDestructor<MyArray> x{{{"foo", "bar", "baz"}}};
+ // This only works in clang, not in gcc:
+ // static absl::NoDestructor<MyArray> x({{"foo", "bar", "baz"}});
+ return *x;
+}
+
+typedef std::vector<int> MyVector;
+const MyVector& Vector() {
+ static absl::NoDestructor<MyVector> x{{1, 2, 3}};
+ return *x;
+}
+
+const int& Int() {
+ static absl::NoDestructor<int> x;
+ return *x;
+}
+
+TEST(NoDestructorTest, StaticPattern) {
+ EXPECT_TRUE(
+ std::is_trivially_destructible<absl::NoDestructor<std::string>>::value);
+ EXPECT_TRUE(
+ std::is_trivially_destructible<absl::NoDestructor<MyArray>>::value);
+ EXPECT_TRUE(
+ std::is_trivially_destructible<absl::NoDestructor<MyVector>>::value);
+ EXPECT_TRUE(std::is_trivially_destructible<absl::NoDestructor<int>>::value);
+
+ EXPECT_EQ(*Str0(), "");
+ Str0()->append("foo");
+ EXPECT_EQ(*Str0(), "foo");
+
+ EXPECT_EQ(std::string(Str1()), "Str2_Str1");
+
+ EXPECT_EQ(Str2(), "Str2");
+ EXPECT_EQ(Str2Copy(), "Str2");
+
+ EXPECT_THAT(Array(), testing::ElementsAre("foo", "bar", "baz"));
+
+ EXPECT_THAT(Vector(), testing::ElementsAre(1, 2, 3));
+
+ EXPECT_EQ(0, Int()); // should get zero-initialized
+}
+
+#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION
+// This would fail to compile if Class Template Argument Deduction was not
+// provided for absl::NoDestructor.
+TEST(NoDestructorTest, ClassTemplateArgumentDeduction) {
+ absl::NoDestructor i(1);
+ static_assert(std::is_same<decltype(i), absl::NoDestructor<int>>::value,
+ "Expected deduced type to be int.");
+}
+#endif
+
+} // namespace