Skip to content

Commit

Permalink
TypeErasedRef
Browse files Browse the repository at this point in the history
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
  • Loading branch information
praihan authored and facebook-github-bot committed Jun 6, 2024
1 parent a0aa1db commit 7db5a06
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 0 deletions.
94 changes: 94 additions & 0 deletions thrift/lib/cpp2/util/TypeErasedRef.h
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <type_traits>
#include <typeinfo>
#include <utility>

#include <folly/Traits.h>
#include <folly/lang/SafeAssert.h>

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<T>() 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 <class T>
bool holds_alternative() const noexcept {
return type() == typeid(T);
}

template <class T>
const folly::remove_cvref_t<T>& value() const {
if (!holds_alternative<T>()) {
throw std::bad_cast();
}
return value_unchecked<T>();
}

template <class T>
const folly::remove_cvref_t<T>& value_unchecked() const noexcept {
FOLLY_SAFE_DCHECK(
holds_alternative<T>(),
"Tried to call value_unchecked() on TypeErasedRef with incompatible type");
return *reinterpret_cast<const folly::remove_cvref_t<T>*>(ptr_);
}

TypeErasedRef(const TypeErasedRef& other) noexcept = default;
TypeErasedRef& operator=(const TypeErasedRef& other) noexcept = default;

template <class T>
static TypeErasedRef of(folly::remove_cvref_t<T>&&) = delete;

template <class T>
static TypeErasedRef of(const folly::remove_cvref_t<T>& object) noexcept {
return fromTypeInfoUnchecked(
static_cast<const void*>(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
42 changes: 42 additions & 0 deletions thrift/lib/cpp2/util/test/TypeErasedRefTest.cpp
Original file line number Diff line number Diff line change
@@ -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 <folly/portability/GTest.h>

#include <thrift/lib/cpp2/util/TypeErasedRef.h>

using apache::thrift::util::TypeErasedRef;

TEST(TypeErasedRefTest, Basic) {
std::string str = "hello";
auto ref1 = TypeErasedRef::of<std::string>(str);
auto ref1Copy = ref1;

std::string str2 = "world";
auto ref2 = TypeErasedRef::of<std::string>(str2);
EXPECT_EQ(ref2.value<std::string>(), 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<std::string>());
EXPECT_FALSE(ref.holds_alternative<int>());
EXPECT_EQ(ref.value<std::string>(), str);
EXPECT_EQ(ref.value<const std::string&>(), str);
EXPECT_THROW(ref.value<int>(), std::bad_cast);
}
}

0 comments on commit 7db5a06

Please sign in to comment.