blob: 858a0695624970a1641bb329806ddc320b67fd52 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// 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.
#ifndef FUZZTEST_FUZZTEST_INTERNAL_POLYMORPHIC_VALUE_H_
#define FUZZTEST_FUZZTEST_INTERNAL_POLYMORPHIC_VALUE_H_
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/meta.h"
namespace fuzztest::internal {
// Trivially copyable/destructible class that handles type erasure for a value.
// It does not own the input value. That is, it will not manage the lifetime of
// the object or implement deep copies.
// It is a building block for other abstractions.
template <typename... Visitor>
class VisitableValue {
public:
VisitableValue() : vtable_(nullptr), value_(nullptr) {}
template <typename T>
explicit VisitableValue(std::in_place_t, T* value) {
vtable_ = &VTableFor<T>;
value_ = value;
}
bool has_value() const { return vtable_ != nullptr; }
template <typename T>
bool Has() const {
return vtable_->type_id == type_id<T>;
}
template <typename T>
T& GetAs() {
FUZZTEST_INTERNAL_CHECK(Has<T>(), "Wrong type!");
return *static_cast<T*>(value_);
}
template <typename T>
const T& GetAs() const {
FUZZTEST_INTERNAL_CHECK(Has<T>(), "Wrong type!");
return *static_cast<T*>(value_);
}
template <typename V, typename... Args,
typename = std::enable_if_t<always_true<V> &&
(std::is_same_v<V, Visitor> || ...)>>
auto Visit(V visitor, Args&&... args) {
return std::get<GetVisitIndex<V>()>(vtable_->visitors)(
visitor, value_, std::forward<Args>(args)...);
}
template <typename V, typename... Args,
typename = std::enable_if_t<always_true<V> &&
(std::is_same_v<V, Visitor> || ...)>>
auto Visit(V visitor, Args&&... args) const {
return std::get<GetVisitIndex<V>()>(vtable_->visitors)(
visitor, static_cast<const void*>(value_), std::forward<Args>(args)...);
}
private:
template <typename T, typename R, typename V, typename... Args>
static R DispatchImpl(
V v, std::conditional_t<std::is_const_v<T>, const void, void>* p,
Args... args) {
return v(*static_cast<T*>(p), std::forward<Args>(args)...);
}
template <typename T, typename R, typename V, typename... Args>
static constexpr auto GetDispatchImpl(R (V::*)(T&, Args...))
-> decltype(&DispatchImpl<T, R, V, Args...>) {
return DispatchImpl<T, R, V, Args...>;
}
template <typename T, typename V>
static constexpr auto GetDispatch()
-> decltype(GetDispatchImpl(&V::template operator()<T>)) {
return GetDispatchImpl(&V::template operator()<T>);
}
template <typename V>
using VisitImpl = decltype(GetDispatch<int, V>());
struct VTable {
TypeId type_id;
std::tuple<VisitImpl<Visitor>...> visitors;
};
template <typename T>
static constexpr VTable VTableFor = {type_id<T>,
{GetDispatch<T, Visitor>()...}};
template <typename V, size_t i = 0>
static constexpr auto GetVisitIndex() {
if constexpr (std::is_same_v<
VisitImpl<V>,
std::tuple_element_t<i, decltype(VTable::visitors)>>) {
return i;
} else {
return GetVisitIndex<V, i + 1>();
}
}
const VTable* vtable_;
void* value_;
};
struct DestroyVisitor {
template <typename T>
void operator()(T& v) {
delete &v;
}
};
template <typename P>
struct CopyVisitor {
template <typename T>
P operator()(const T& v) {
return P(std::in_place, v);
}
};
// A class similar to std::any with the following differences:
// - You declare which operations can be applied on the object via Visitors.
// - To access the object you use Has<T>()/GetAs<T>() instead of any_cast.
//
// A visitor is a callable object with a signature like:
// template <typename T>
// R operator()(T&, Args...)
//
// Visitors allow accessing the type erased value in a generic manner.
// PolymorphicValue will provide the dynamic dispatch necessary for all the
// visitors to work.
//
// Eg:
// struct PrintVisitor {
// template <typename T>
// void operator()(const T& v) {
// std::cout << v;
// }
// };
// PolymorphicValue<PrintVisitor> v;
// v = 1;
// v.Visit(PrintVisitor{}); // Prints "1"
// v = 1.5;
// v.Visit(PrintVisitor{}); // Prints "1.5"
// v = "ABC";
// v.Visit(PrintVisitor{}); // Prints "ABC"
template <typename... Visitor>
class PolymorphicValue
: private VisitableValue<DestroyVisitor,
CopyVisitor<PolymorphicValue<Visitor...>>,
Visitor...> {
using Base =
VisitableValue<DestroyVisitor, CopyVisitor<PolymorphicValue>, Visitor...>;
public:
PolymorphicValue() {}
template <typename T>
explicit PolymorphicValue(std::in_place_t, T value)
: Base(std::in_place, new T(std::move(value))) {}
PolymorphicValue(const PolymorphicValue& other) {
if (other.has_value()) {
*this = other.Visit(CopyVisitor<PolymorphicValue>{});
}
}
PolymorphicValue(PolymorphicValue&& other) { *this = std::move(other); }
PolymorphicValue& operator=(const PolymorphicValue& other) {
*this = PolymorphicValue(other);
return *this;
}
PolymorphicValue& operator=(PolymorphicValue&& other) {
if (this == &other) return *this;
if (has_value()) Visit(DestroyVisitor{});
static_cast<Base&>(*this) =
std::exchange(static_cast<Base&>(other), Base{});
return *this;
}
~PolymorphicValue() {
if (has_value()) Visit(DestroyVisitor{});
}
using Base::GetAs;
using Base::Has;
using Base::has_value;
using Base::Visit;
};
} // namespace fuzztest::internal
#endif // FUZZTEST_FUZZTEST_INTERNAL_POLYMORPHIC_VALUE_H_