From 7db5a066e9d011c19e1df8b3e07aa37e3b7e9c99 Mon Sep 17 00:00:00 2001 From: Pranjal Raihan Date: Thu, 6 Jun 2024 16:12:48 -0700 Subject: [PATCH] TypeErasedRef Summary: The doc block explains it well. It's essentially a `void*` with an accompanying `std::type_info` which provides some basic runtime type safety. Reviewed By: sethdelliott Differential Revision: D58217404 fbshipit-source-id: 609414a0e21fe59c514d8d1340a6bc5c95a34a89 --- thrift/lib/cpp2/util/TypeErasedRef.h | 94 +++++++++++++++++++ .../lib/cpp2/util/test/TypeErasedRefTest.cpp | 42 +++++++++ 2 files changed, 136 insertions(+) create mode 100644 thrift/lib/cpp2/util/TypeErasedRef.h create mode 100644 thrift/lib/cpp2/util/test/TypeErasedRefTest.cpp diff --git a/thrift/lib/cpp2/util/TypeErasedRef.h b/thrift/lib/cpp2/util/TypeErasedRef.h new file mode 100644 index 00000000000..27acfa11be5 --- /dev/null +++ b/thrift/lib/cpp2/util/TypeErasedRef.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +namespace apache::thrift::util { + +/** + * A type-safe type-erased const reference to an object. + * + * Unlike TypeErasedValue, the referred-to object is not owned by this class. + * The user must ensure that the aforementioned object outlives any access + * through an object of this class. + * + * Access is provided through the templated value() method which checks that + * the referred-to object is indeed of type T. This is the main difference + * between TypeErasedRef and const void* — the latter has no safety checks. + * + * TypeErasedRef is copyable. The copy points to the same object as the + * original. + */ +class TypeErasedRef { + public: + const std::type_info& type() const noexcept { return *typeInfo_; } + const void* ptr() const noexcept { return ptr_; } + + template + bool holds_alternative() const noexcept { + return type() == typeid(T); + } + + template + const folly::remove_cvref_t& value() const { + if (!holds_alternative()) { + throw std::bad_cast(); + } + return value_unchecked(); + } + + template + const folly::remove_cvref_t& value_unchecked() const noexcept { + FOLLY_SAFE_DCHECK( + holds_alternative(), + "Tried to call value_unchecked() on TypeErasedRef with incompatible type"); + return *reinterpret_cast*>(ptr_); + } + + TypeErasedRef(const TypeErasedRef& other) noexcept = default; + TypeErasedRef& operator=(const TypeErasedRef& other) noexcept = default; + + template + static TypeErasedRef of(folly::remove_cvref_t&&) = delete; + + template + static TypeErasedRef of(const folly::remove_cvref_t& object) noexcept { + return fromTypeInfoUnchecked( + static_cast(std::addressof(object)), typeid(T)); + } + + static TypeErasedRef fromTypeInfoUnchecked( + const void* ptr, const std::type_info& typeInfo) noexcept { + return TypeErasedRef(ptr, typeInfo); + } + + private: + TypeErasedRef(const void* ptr, const std::type_info& typeInfo) noexcept + : ptr_(ptr), typeInfo_(&typeInfo) {} + + const void* ptr_; + const std::type_info* typeInfo_; +}; + +} // namespace apache::thrift::util diff --git a/thrift/lib/cpp2/util/test/TypeErasedRefTest.cpp b/thrift/lib/cpp2/util/test/TypeErasedRefTest.cpp new file mode 100644 index 00000000000..a33339f6487 --- /dev/null +++ b/thrift/lib/cpp2/util/test/TypeErasedRefTest.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * 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 + +#include + +using apache::thrift::util::TypeErasedRef; + +TEST(TypeErasedRefTest, Basic) { + std::string str = "hello"; + auto ref1 = TypeErasedRef::of(str); + auto ref1Copy = ref1; + + std::string str2 = "world"; + auto ref2 = TypeErasedRef::of(str2); + EXPECT_EQ(ref2.value(), str2); + ref2 = ref1; + + for (auto& ref : {ref1, ref1Copy, ref2}) { + EXPECT_EQ(std::uintptr_t(ref.ptr()), std::uintptr_t(&str)); + EXPECT_EQ(ref.type(), typeid(std::string)); + EXPECT_TRUE(ref.holds_alternative()); + EXPECT_FALSE(ref.holds_alternative()); + EXPECT_EQ(ref.value(), str); + EXPECT_EQ(ref.value(), str); + EXPECT_THROW(ref.value(), std::bad_cast); + } +}