RenderCore/Base/CKVariant.h (351 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <RenderCore/CKDefines.h>
#if CK_NOT_SWIFT
#include <array>
#include <cassert>
#include <new>
#include <tuple>
#include <type_traits>
#include <utility>
#include <functional>
namespace CK {
template <typename... Types> class Variant;
namespace VariantDetail {
#pragma mark - Typelist
/**
Represents a list of types known at compile-time, e.g. \code Typelist<bool, short, int> \endcode
*/
template <typename... Elements> class Typelist {};
#pragma mark - IsEmpty
/**
A meta-function that determines if a given typelist is empty.
\code
IsEmpty<Typelist<bool, short, int>>::value == false
IsEmpty<Typelist<>>::value == true
\endcode
*/
template <typename List> class IsEmpty {
public:
static constexpr auto value = false;
};
template <> class IsEmpty<Typelist<>> {
public:
static constexpr auto value = true;
};
#pragma mark - Front
template <typename List> class FrontT;
template <typename Head, typename... Tail> class FrontT<Typelist<Head, Tail...>> {
public:
using Type = Head;
};
/**
A meta-function returning the first element of a typelist.
\code
using T = Front<Typelist<bool, short, int>>; // T == bool
\endcode
*/
template <typename List> using Front = typename FrontT<List>::Type;
#pragma mark - PopFront
template <typename List> class PopFrontT;
template <typename Head, typename... Tail> class PopFrontT<Typelist<Head, Tail...>> {
public:
using Type = Typelist<Tail...>;
};
/**
A meta-function that removes the first element from the typelist.
\code
using Ts = PopFront<Typelist<bool, short, int>>; // T == Typelist<short, int>
\endcode
*/
template <typename List> using PopFront = typename PopFrontT<List>::Type;
#pragma mark - LargestType
template <typename List, bool Empty = IsEmpty<List>::value> class LargestTypeT;
template <typename List> class LargestTypeT<List, false> {
private:
using Contender = Front<List>;
using Best = typename LargestTypeT<PopFront<List>>::Type;
public:
using Type = std::conditional_t<sizeof(Contender) >= sizeof(Best), Contender, Best>;
};
template <typename List> class LargestTypeT<List, true> {
public:
using Type = char; // Guaranteed not to be larger than any other type.
};
/**
A meta-function returning the largest type in a typelist.
\code
using T = LargestType<Typelist<bool, short, int>>; // T == int
\endcode
*/
template <typename List> using LargestType = typename LargestTypeT<List>::Type;
#pragma mark - NthElement
template <typename List, unsigned N> class NthElementT : public NthElementT<PopFront<List>, N - 1> {};
template <typename List> class NthElementT<List, 0> : public FrontT<List> {};
/**
A meta-function returning the Nth element of a typelist.
\code
using T = NthElement<Typelist<bool, short, int>, 1>; // T == short
\endcode
*/
template <typename List, unsigned N> using NthElement = typename NthElementT<List, N>::Type;
#pragma mark - FindIndexOf
constexpr auto notFound = std::numeric_limits<std::size_t>::max();
constexpr auto ambiguous = std::numeric_limits<std::size_t>::max() - 1;
/**
Returns an index for type \c T in a template parameter pack \c Ts.
\code
auto i = findIndexImpl<short, bool, short, int>(); // i == 1
\endcode
This function should not be used directly. Consider using the \c IndexOfT meta-function instead.
\return The index of \c T in \c Ts if it occurs only once; \c ambiguous if \c T occurs more than once or \c notFound
if \c T was not found.
*/
template <typename T, typename... Ts> constexpr auto findIndexImpl() -> std::size_t
{
// For T == short and Ts == {bool, short, int}, this array would be {false, true, false}
constexpr auto matches = std::array<bool, sizeof...(Ts)>{{std::is_same<T, Ts>::value...}};
auto result = notFound;
for (std::size_t i = 0; i < sizeof...(Ts); i++) {
if (matches[i]) {
if (result != notFound) {
result = ambiguous;
}
result = i;
}
}
return result;
}
template <std::size_t Index> struct FindIndexChecked : std::integral_constant<std::size_t, Index> {
static_assert(Index != notFound, "The specified type is not found");
static_assert(Index != ambiguous, "The specified type is ambiguous");
};
/**
A meta-function returning an index for type \c T in a template parameter pack \c Ts.
*/
template <typename T, typename... Ts> using IndexOfT = FindIndexChecked<findIndexImpl<T, Ts...>()>;
#pragma mark - VariantStorage
/**
Class responsible for providing storage big enough to accomodate any alternative from \c Types and tracks the currently
active alternative using a discriminator.
*/
template <typename... Types> class VariantStorage {
public:
auto getDiscriminator() const -> unsigned
{
return _discriminator;
}
auto setDiscriminator(unsigned d) -> void
{
_discriminator = d;
}
auto getRawBuffer() -> void *
{
return _buffer;
}
auto getRawBuffer() const -> const void *
{
return _buffer;
}
template <typename T> auto getBufferAs() -> T *
{
// std::launder()
return reinterpret_cast<T *>(_buffer);
}
template <typename T> auto getBufferAs() const -> T const *
{
// std::launder()
return reinterpret_cast<T const *>(_buffer);
}
private:
using LargestT = LargestType<Typelist<Types...>>;
alignas(Types...) unsigned char _buffer[sizeof(LargestT)];
unsigned _discriminator = 0;
};
#pragma mark - VariantChoice
/**
Class responsible for handling a particular alternative \c T in a variant.
\discussion
The handling includes:
\li – Construction and assignment from values of \c T
\li – Destruction of values of \c T
\li – Assigning the value for the alternative \em discriminator (see \c VariantStorage )
The non-type template parameter \c N is a \em reverse index of the alternative in the \c Types parameter pack, i.e. for
\code Types == {bool, short, int} \endcode
a \c VariantChoice corresponding to \c bool would be
\code VariantChoice<3, bool, short, int> \endcode
The reverse index is being used so the terminating condition for the recursive inheritance (see below) does not depend
on the \c N itself.
In addition to that, \c VariantChoice uses recursive inheritance so that it actually handles \em all alternatives that
precede this one in \c Types i.e. for
\code VariantChoice<3, bool, short, int> \endcode
the inheritance chain would be
\code
VariantChoice<3, bool, short, int> -> VariantChoice<2, bool, short, int> -> VariantChoice<1, bool, short, int>...
\endcode
terminating with a degenerate case of \c VariantChoice<0> which is undefined. In the end, there's going to be a \c
VariantChoice instantiation for every type in \c Types .
*/
template <unsigned N, typename... Types> class VariantChoice : public VariantChoice<N - 1, Types...> {
using Derived = Variant<Types...>;
using T = NthElement<Typelist<Types...>, sizeof...(Types) - N>;
public:
VariantChoice() {}
VariantChoice(const T &value)
{
new (getDerived().getRawBuffer()) T{value};
getDerived().setDiscriminator(Discriminator);
}
VariantChoice(T &&value)
{
new (getDerived().getRawBuffer()) T{std::move(value)};
getDerived().setDiscriminator(Discriminator);
}
// Inherit constructors from the rest of the alternatives
using VariantChoice<N - 1, Types...>::VariantChoice;
/// Calls the destructor of \c T, if it is the current alternative, otherwise delegates to the base class.
auto destroy() -> void
{
if (getDerived().getDiscriminator() == Discriminator) {
getDerived().template getBufferAs<T>()->~T();
return;
}
VariantChoice<N - 1, Types...>::destroy();
}
auto operator=(const T &value) -> Derived &
{
auto &derived = getDerived();
if (derived.getDiscriminator() == Discriminator) {
*derived.template getBufferAs<T>() = value;
} else {
derived.destroy();
new (derived.getRawBuffer()) T{value};
derived.setDiscriminator(Discriminator);
}
return derived;
}
auto operator=(T &&value) -> Derived &
{
auto &derived = getDerived();
if (derived.getDiscriminator() == Discriminator) {
*derived.template getBufferAs<T>() = std::move(value);
} else {
derived.destroy();
new (derived.getRawBuffer()) T{std::move(value)};
derived.setDiscriminator(Discriminator);
}
return derived;
}
// Inherit assignment operators from the rest of the alternatives
using VariantChoice<N - 1, Types...>::operator=;
protected:
/// Discriminator value used for the alternative that holds an instance of \c T (see \c VariantStorage )
static constexpr unsigned Discriminator =
sizeof...(Types) - N + 1; // Start with 1 since 0 is reserved for empty variants
private:
/// CRTP helpers
auto getDerived() -> Derived &
{
return *static_cast<Derived *>(this);
}
auto getDerived() const -> const Derived &
{
return *static_cast<Derived const *>(this);
}
};
template <typename... Types> class VariantChoice<0, Types...> {
public:
auto destroy() -> void {}
};
#pragma mark - VariantMatch
/**
Recursively matches a variant against a given matcher.
Do not use this directly, prefer using \c Variant::match instead.
\param variant A variant to match.
\param matcher A function-like object that is able to handle all the alternatives in the variant.
\param _ An unused typelist that is used to pass the list of the alternatives still to be matched, decomposed into a
head and a tail.
\return The result of invoking \c matcher with the current alternative.
*/
template <typename Result, typename V, typename Matcher, typename Head, typename... Tail>
auto variantMatchImpl(V &&variant, Matcher &&matcher, Typelist<Head, Tail...>) -> Result
{
if (variant.template is<Head>()) {
return static_cast<Result>(matcher(variant.template get<Head>()));
}
// Try to match the rest of the alternatives
return variantMatchImpl<Result>(std::forward<V>(variant), std::forward<Matcher>(matcher), Typelist<Tail...>{});
}
template <typename Result, typename V, typename Matcher> auto variantMatchImpl(V &&, Matcher &&, Typelist<>) -> Result
{
// Getting here means the currently stored alternative doesn't match anything from the typelist. The only way this
// could happen is trying to match an empty variant which is forbidden.
abort();
}
#pragma mark - MatchResult
template <typename Result, typename Matcher, typename... ElementTypes> class MatchResultT {
public:
using Type = Result;
};
/// A placeholder type for cases when the result type of matching is determined automatically.
class ComputedResultType;
template <typename Matcher, typename T> using MatchElementResult = decltype(std::declval<Matcher>()(std::declval<T>()));
template <typename Matcher, typename... ElementTypes> class MatchResultT<ComputedResultType, Matcher, ElementTypes...> {
public:
using Type = std::common_type_t<MatchElementResult<Matcher, ElementTypes>...>;
};
/**
A meta-function returning the result type of matching a variant holding \c ElementTypes as its alternatives, against an
instance of \c Matcher.
If \c Result is different from \c ComputedResultType , it is returned immediately; otherwise, a type, all result types
of invoking \c Matcher with each type from \c ElementTypes can be converted to, is returned instead.
Consider the example. For the following matcher:
\code
struct M {
auto operator()(int) -> const char *;
auto operator()(std::string) -> std::string;
}
\endcode
and the \c ElementTypes of:
\code
{int, std::string}
\endcode
The result type of invoking the matcher with \c int would be \c const \c char \c * , while the result type of invoking
the matcher with \c std::string would be an \c std::string . Thus, the common type for \c const \c char \c * and \c
std::string would be \c std::string .
*/
template <typename Result, typename Matcher, typename... ElementTypes>
using MatchResult = typename MatchResultT<Result, Matcher, ElementTypes...>::Type;
#pragma mark - Overloaded
template <typename T>
using ConditionalFunctionWrapper = typename std::conditional<
std::is_pointer<T>::value && std::is_function<typename std::remove_pointer<T>::type>::value,
std::function<typename std::remove_pointer<T>::type>,
T
>::type;
/**
Struct template that aggregates multiple callables into a single callable by inherting from all of them.
For the following \c Fs :
\code
[](int i) { ... },
[](double d) { ... }
\endcode
the resulting callable would be:
\code
struct Overloaded {
auto operator()(int i) { ... }
auto operator()(double d) { ... }
}
\endcode
Thus, the resulting callable can be invoked with both \c int and \c double arguments.
*/
template <typename... Fs> struct Overloaded : ConditionalFunctionWrapper<Fs>... {
Overloaded(Fs... fs) : ConditionalFunctionWrapper<Fs>{fs}... {}
};
} // namespace VariantDetail
#pragma mark - Variant
/**
Class template representing a sum type for all \c Types .
\c CK::Variant is useful for representing a set of mutually exclusive alternatives represented by instances of \c Types
. For example:
\code
CK::Variant<int, double, std::string>
\endcode
can store either an \c int , a \c double or a \c std::string , but only one of them at a time.
In order to access the currently active alternative, a set of \c match member functions is provided, e.g.
\code
CK::Variant<int, double, std::string> v = ...;
v.match(
[](int i) { // Process int value },
[](double d) { // Process double value },
[](const std::string &s) { // Process string value }
);
\endcode
*/
template <typename... Types>
class Variant : private VariantDetail::VariantStorage<Types...>,
private VariantDetail::VariantChoice<sizeof...(Types), Types...> {
template <unsigned, typename... OtherTypes> friend class VariantDetail::VariantChoice;
public:
// Inherit constructors from each of the types in Types
using VariantDetail::VariantChoice<sizeof...(Types), Types...>::VariantChoice;
// Inherit assignment operators from each of the types in Types
using VariantDetail::VariantChoice<sizeof...(Types), Types...>::operator=;
/**
Provides compile-time indexed access to the types of the variant's alternatives
\code
using T = CK::Variant<bool, short, int>::AlternativeAt<1>; // T == short
\endcode
*/
template <unsigned i>
using AlternativeAt = VariantDetail::NthElement<VariantDetail::Typelist<Types...>, i>;
Variant() = default;
Variant(const Variant& other) {
if (other.getDiscriminator() != 0) {
other.match([this](const auto& rhs){
*this = rhs;
});
}
}
Variant(Variant&& other) {
if (other.getDiscriminator() != 0) {
other.match([this](auto&& rhs){
*this = std::move(rhs);
});
}
}
Variant& operator=(const Variant& other)
{
if (other.getDiscriminator() != 0) {
other.match([this](const auto& rhs){
*this = rhs;
});
} else {
destroy();
}
return *this;
}
Variant& operator=(Variant&& other) {
if (other.getDiscriminator() != 0) {
other.match([this](auto&& rhs){
*this = std::move(rhs);
});
} else {
destroy();
}
return *this;
}
~Variant()
{
destroy();
}
/**
Checks if the alternative corresponding to \c T is currently stored in the variant.
*/
template <typename T> auto is() const -> bool
{
constexpr auto indexOfT = VariantDetail::IndexOfT<T, Types...>::value;
return this->getDiscriminator() ==
VariantDetail::VariantChoice<sizeof...(Types) - indexOfT, Types...>::Discriminator;
}
/**
Checks if the variant has a value.
*/
auto hasValue() const -> bool
{
return this->getDiscriminator() != 0;
}
/**
Matches the variant against a given matcher.
\param matcher A callable that can be invoked with any type in \c Types . If \c matcher cannot be invoked with one or
more types in \c Types a compilation error is raised.
*/
template <typename R = VariantDetail::ComputedResultType, typename Matcher> auto match(Matcher &&matcher) &
{
using Result = VariantDetail::MatchResult<R, Matcher, Types &...>;
return VariantDetail::variantMatchImpl<Result>(
*this, std::forward<Matcher>(matcher), VariantDetail::Typelist<Types...>{});
}
template <typename R = VariantDetail::ComputedResultType, typename Matcher> auto match(Matcher &&matcher) const &
{
using Result = VariantDetail::MatchResult<R, Matcher, Types const &...>;
return VariantDetail::variantMatchImpl<Result>(
*this, std::forward<Matcher>(matcher), VariantDetail::Typelist<Types...>{});
}
template <typename R = VariantDetail::ComputedResultType, typename Matcher> auto match(Matcher &&matcher) &&
{
using Result = VariantDetail::MatchResult<R, Matcher, Types &&...>;
return VariantDetail::variantMatchImpl<Result>(
std::move(*this), std::forward<Matcher>(matcher), VariantDetail::Typelist<Types...>{});
}
/**
Matches the variant against a given set of matchers.
\param matchers A set of callables that can be invoked with any type in \c Types . If a matcher for one or more types
in \c Types is missing, a compilation error is raised.
*/
template <typename... Matchers> auto match(Matchers &&... matchers) &
{
return match(VariantDetail::Overloaded<Matchers...>{matchers...});
}
template <typename... Matchers> auto match(Matchers &&... matchers) const &
{
return match(VariantDetail::Overloaded<Matchers...>{matchers...});
}
template <typename... Matchers> auto match(Matchers &&... matchers) &&
{
return std::move(*this).match(VariantDetail::Overloaded<Matchers...>{matchers...});
}
/**
Compares this variant to a value of \c T.
The variant is considered equal to the value if it currently holds the alternative of \c T and the alternative is
equal to the provided value. The equality for the alternatives is determined by invoking the equality operator,
therefore, if the equality operator is not provided by the alternative, a compilation error is raised.
*/
template <typename T> auto operator==(const T &value) const -> bool
{
if (!is<T>()) {
return false;
}
return get<T>() == value;
}
/**
Compares this variant to another.
Variants are considered equal if both hold the same alternative, and the alternatives are equal. The equality for the
alternatives is determined by invoking the equality operator, therefore, if the equality operator is not provided by
the alternative, a compilation error is raised.
\param other A variant to compare to.
*/
auto operator==(const Variant<Types...> &other) const -> bool
{
if (this->getDiscriminator() != other.getDiscriminator()) {
return false;
}
return other.match([this](const auto &rhs) {
using T = std::remove_const_t<std::remove_reference_t<decltype(rhs)>>;
return this->get<T>() == rhs;
});
}
auto operator!=(const Variant<Types...> &other) const -> bool
{
return !(*this == other);
}
private:
template <typename Result, typename V, typename Matcher, typename Head, typename... Tail>
friend auto VariantDetail::variantMatchImpl(V &&variant, Matcher &&matcher, VariantDetail::Typelist<Head, Tail...>)
-> Result;
template <typename T> auto get() & -> T &
{
assert(is<T>());
return *this->template getBufferAs<T>();
}
template <typename T> auto get() const & -> const T &
{
assert(is<T>());
return *this->template getBufferAs<T>();
}
template <typename T> auto get() && -> T &&
{
assert(is<T>());
return std::move(*this)->template getBufferAs<T>();
}
auto destroy() -> void
{
VariantDetail::VariantChoice<sizeof...(Types), Types...>::destroy();
this->setDiscriminator(0);
}
};
} // namespace CK
#endif