fatal/container/legacy_variant.h (1,523 lines of code) (raw):
/*
* Copyright (c) 2016, 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.
*/
#ifndef FATAL_INCLUDE_fatal_container_legacy_variant_h
#define FATAL_INCLUDE_fatal_container_legacy_variant_h
#include <fatal/container/optional.h>
#include <fatal/container/unitary_union.h>
#include <fatal/functional/functional.h>
#include <fatal/math/numerics.h>
#include <fatal/portability.h>
#include <fatal/type/conditional.h>
#include <fatal/type/deprecated/type_list.h>
#include <fatal/type/traits.h>
#include <functional>
#include <memory>
#include <utility>
#include <cassert>
FATAL_DIAGNOSTIC_PUSH
FATAL_GCC_DIAGNOSTIC_IGNORED_SHADOW_IF_BROKEN
/**
* #################
* # READ ME FIRST #
* #################
*
* This file implements a memory efficient custom allocated variant.
*
* NOTE: this implementation is undergoing a major cleanup.
* The interface should be considered mostly stable, though.
*
* This implementation is rather complex and involves lots of compile-time
* template metaprogramming so. To keep it simple you're encouraged to start
* with the variant declaration itself before anything else.
*
* To quickly get there, search for 'START HERE'.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
namespace fatal {
/**
* #######################
* # 'ALLOCATION POLICY' #
* #######################
*
* This is the default implementation for the allocation policy
* given a type T it decides whether to allocate it automatically
* or dynamically.
*
* A type T is automatically allocated if it is a scalar, or if its
* size in bytes is smaller than ax + b, where x is the size in bytes
* of a pointer to T, a is allocateMultiplier (defaults to 1) and
* b is allocateIncrement (defaults to the size in bytes of size_t).
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
namespace detail {
template <typename std::size_t Threshold>
struct variant_automatic_policy_impl {
template <
typename T,
typename = enable_when::any_true<
std::is_scalar<T>,
bool_constant<(sizeof(T) <= Threshold)>
>
>
static std::true_type sfinae(T *);
template <typename...>
static std::false_type sfinae(...);
template <typename T>
using type = logical::all<
is_complete<T>,
decltype(sfinae(static_cast<T *>(nullptr)))
>;
};
} // namespace detail {
template <
std::size_t allocateMultiplier = 1,
std::size_t allocateIncrement = sizeof(std::size_t)
>
class default_allocation_policy {
template <typename T>
using allocate_threshold = size_constant<
allocateMultiplier * sizeof(void *) + allocateIncrement
>;
public:
template <typename T>
using dynamic = bool_constant<!(
detail::variant_automatic_policy_impl<allocate_threshold<T>::value>
::template type<T>::value
)>;
};
template <bool AlwaysDynamic>
class fixed_allocation_policy {
template <typename T>
struct impl {
static_assert(
AlwaysDynamic || is_complete<T>::value,
"can't allocate an incomplete type using automatic storage duration"
);
using type = bool_constant<AlwaysDynamic>;
};
public:
template <typename T>
using dynamic = typename impl<T>::type;
};
using dynamic_allocation_policy = fixed_allocation_policy<true>;
using automatic_allocation_policy = fixed_allocation_policy<false>;
/**
* ####################
* # 'STORAGE POLICY' #
* ####################
*
* This is the default implementation of the storage policy. Its goal is to
* orchestrate the interactions with both the allocation policy and the
* allocator, providing a uniform interface for the underlying storage
* used to store the values.
*
* This policy provides the following methods:
*
* - these methods are analogous to std::allocator_traits' ones:
*
* static void allocate(Alloc const &, typename storage_type<T>::type &);
* static void deallocate(Alloc const &, typename storage_type<T>::type &);
* static T &construct(Alloc const &, typename storage_type<T>::type &, ...);
* static void destroy(Alloc const &, typename storage_type<T>::type &);
*
* - move_over() is provided to aid implementation of move construction, move
* assignment and swapping:
*
* static void move_over(
* typename storage_type<T>::type &from
* typename storage_type<T>::type &to
* );
*
* - get() is provided to abstract the way the actual stored value is obtained,
* from its internal storage representation:
*
* static T const &get(typename storage_type<T>::type const &);
* static T &get(typename storage_type<T>::type &);
*
* TAllocator is any STL-style allocator which will be rebound to whatever
* type the storage policy must handle. It will only be used for dynamically
* allocated types.
*
* TAllocationPolicy decides which allocation method will be used for a given
* type. It needs to provide a single constant
*
* template <typename T> using dynamic = std::integral_constant<bool, ???>;
*
* which returns true when dynamic allocation must be used to store the type
* T, or false when automatic allocation must be used to store the same type.
*
* A default implementation of the allocation policy is provided. Search for
* 'ALLOCATION POLICY' for more details.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename TAllocator = std::allocator<void>,
typename TAllocationPolicy = default_allocation_policy<>,
bool IsCopyable = true
>
struct legacy_storage_policy {
using allocator_type = TAllocator;
using allocator_traits = std::allocator_traits<allocator_type>;
using allocation_policy = TAllocationPolicy;
/**
* Tells whether copy construction and copy assignment should be allowed by
* the variant. Checked at compile time.
*/
constexpr static bool is_copyable() { return IsCopyable; }
template <typename T>
using allocate_dynamically = typename allocation_policy::template dynamic<T>;
/**
* This class abstracts the interaction with TAllocationPolicy. It provides
* a member type for the internal storage representation of the given type T,
* and a boolean telling whether this representation uses automatic or
* dynamic storage.
*/
template <typename T>
class storage_type {
static_assert(
!std::is_reference<T>::value,
"default_storage_policy: unable to store references"
);
public:
using dynamic = allocate_dynamically<T>;
static_assert(
is_complete<T>::value || dynamic::value,
"can't allocate an incomplete type using automatic storage duration"
);
// A naked pointer is used for dynamically allocated types. No smart
// pointers are used since we aim for the smallest possible footprint
// (having a copy of the deleter for every pointer is undesired).
using type = conditional<
dynamic::value,
T *, unitary_union<T, false>
>;
};
// Methods for when T is stored using DYNAMIC ALLOCATION.
//
// The methods below are guaranteed to work on `T *` due to:
// typename std::enable_if<
// storage_type<T>::dynamic::value,
// ...
template <typename T, typename std::enable_if<
storage_type<T>::dynamic::value, int
>::type = 0>
static void allocate(
allocator_type const &allocator,
typename storage_type<T>::type &storage
) {
using rebound_alloc = typename allocator_traits::template rebind_alloc<T>;
storage = rebound_alloc(allocator).allocate(1);
}
template <typename T, typename std::enable_if<
storage_type<T>::dynamic::value, int
>::type = 0>
static void deallocate(
allocator_type const &allocator,
typename storage_type<T>::type &storage
) {
using rebound_alloc = typename allocator_traits::template rebind_alloc<T>;
rebound_alloc(allocator).deallocate(storage, 1);
storage = nullptr;
}
template <typename T, typename... Args, typename std::enable_if<
storage_type<T>::dynamic::value, int
>::type = 0>
static T &construct(
allocator_type const &allocator,
typename storage_type<T>::type &storage,
Args &&...args
) {
using rebound_traits = typename allocator_traits::template rebind_traits<T>;
auto allocator_ = typename rebound_traits::allocator_type(allocator);
rebound_traits::construct(allocator_, storage, std::forward<Args>(args)...);
return *storage;
}
template <typename T, typename std::enable_if<
storage_type<T>::dynamic::value, int
>::type = 0>
static void destroy(
allocator_type const &allocator,
typename storage_type<T>::type &storage
) {
using rebound_traits = typename allocator_traits::template rebind_traits<T>;
auto allocator_ = typename rebound_traits::allocator_type(allocator);
rebound_traits::destroy(allocator_, storage);
}
template <typename T, typename std::enable_if<
storage_type<T>::dynamic::value, int
>::type = 0>
static void move_over(
typename storage_type<T>::type &from,
typename storage_type<T>::type &to
) {
to = from;
from = nullptr;
}
template <typename T, typename std::enable_if<
storage_type<T>::dynamic::value, int
>::type = 0>
static T const &get(
typename storage_type<T>::type const &storage
) {
return *storage;
}
template <typename T, typename std::enable_if<
storage_type<T>::dynamic::value, int
>::type = 0>
static T &get(
typename storage_type<T>::type &storage
) {
return *storage;
}
// Methods for when T is stored using AUTOMATIC ALLOCATION.
//
// The methods below are guaranteed to work on `union { T value; };` due to:
// typename std::enable_if<
// !storage_type<T>::dynamic::value,
// ...
template <typename T, typename std::enable_if<
!storage_type<T>::dynamic::value, int
>::type = 0>
static void allocate(
allocator_type const &,
typename storage_type<T>::type &
) {}
template <typename T, typename std::enable_if<
!storage_type<T>::dynamic::value, int
>::type = 0>
static void deallocate(
allocator_type const &,
typename storage_type<T>::type &
) {}
template <typename T, typename... Args, typename std::enable_if<
!storage_type<T>::dynamic::value, int
>::type = 0>
static T &construct(
allocator_type const &,
typename storage_type<T>::type &storage,
Args &&...args
) {
return *new (std::addressof(storage.value)) T(std::forward<Args>(args)...);
}
template <typename T, typename std::enable_if<
!storage_type<T>::dynamic::value, int
>::type = 0>
static void destroy(
allocator_type const &,
typename storage_type<T>::type &storage
) {
storage.value.~T();
}
template <typename T, typename std::enable_if<
!storage_type<T>::dynamic::value, int
>::type = 0>
static void move_over(
typename storage_type<T>::type &from,
typename storage_type<T>::type &to
) {
new (std::addressof(to.value)) T(std::move(from.value));
from.value.~T();
}
template <typename T, typename std::enable_if<
!storage_type<T>::dynamic::value, int
>::type = 0>
static T const &get(
typename storage_type<T>::type const &storage
) {
return storage.value;
}
template <typename T, typename std::enable_if<
!storage_type<T>::dynamic::value, int
>::type = 0>
static T &get(
typename storage_type<T>::type &storage
) {
return storage.value;
}
};
template <
typename Allocator = std::allocator<void>,
typename AllocationPolicy = default_allocation_policy<>,
bool IsCopyable = true
>
using default_storage_policy = legacy_storage_policy<
Allocator, AllocationPolicy, IsCopyable
>;
namespace detail {
namespace variant_impl {
/**
* An internal traits class used by fatal::variant to handle variadic
* recursion and runtime type erasure without the need for virtual calls.
*
* It basically provides a union comprised of all the types that need to
* be stored and a uniform interface to handle such union in a type-safe
* manner.
*
* Try to picture the goal of (some parameters omitted for simplicity)
*
* variadic_union_traits<T1, T2, T3, ..., Tn-1, Tn>
*
* as providing something like
*
* union {
* head: union { storage_policy::storage_type<T1>::type storage; };
* tail: union {
* head: union { storage_policy::storage_type<T2>::type storage; };
* tail: union {
* head: union { storage_policy::storage_type<T3>::type storage; };
* tail: union {
* ...
* head: union { storage_policy::storage_type<Tn-1>::type storage; };
* tail: union { storage_policy::storage_type<Tn>::type storage; };
* };
* };
* };
* };
*
* There are two specializations for this class. One of them is the general
* variadic version (a.k.a. general case) and the other is the terminal
* version (a.k.a. base case).
*
* - General case: it is responsible for checking whether the given type
* relates to the current recursion depth or not:
* - if it does, it delegates the call to a terminal (base case)
* representing the current recursion depth (head);
* - if not, it propagates the call further deep in the recursion (tail).
*
* - Base case: it is responsible for interfacing with the storage policy
* and effectivelly executing any necessary operations on the storage.
*
* Each of the recursion levels are identified by an unique number, a.k.a
* the type_tag, which is the information used both at compile and run-time
* by the general case to determine the right depth of the union that must
* be operated upon.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <typename, typename TSize, TSize, typename...>
struct variadic_union_traits;
// empty case
template <typename TStoragePolicy, typename TSize, TSize Depth>
struct variadic_union_traits<TStoragePolicy, TSize, Depth> {
using size_type = TSize;
union union_type {};
constexpr static size_type end_depth() { return Depth; }
};
// base case
template <typename TStoragePolicy, typename TSize, TSize Depth, typename T>
struct variadic_union_traits<TStoragePolicy, TSize, Depth, T> {
static_assert(std::is_integral<TSize>::value, "TSize must be an integral");
static_assert(!std::is_reference<T>::value, "T can't be a reference");
static_assert(!std::is_const<T>::value, "T can't be const");
using size_type = TSize;
using value_type = T;
using storage_policy = TStoragePolicy;
using allocator_type = typename storage_policy::allocator_type;
using storage_type = typename storage_policy
::template storage_type<value_type>::type;
union union_type {
storage_type storage;
union_type() {}
union_type(union_type const &) {}
union_type(union_type &&) {}
~union_type() {}
};
// calls not dependent on value_type
constexpr static size_type end_depth() { return Depth + 1; }
template <template <typename> class... TTransforms>
static std::type_info const &type(size_type const depth) {
assert(depth == Depth), (void) depth;
return typeid(
typename fatal::try_transform<
fatal::compose<TTransforms...>::template apply
>::template apply<value_type>
);
}
template <typename TVisitor, typename... UArgs>
static void visit(
size_type const depth, union_type const &u, TVisitor &&visitor,
UArgs &&...args
) {
assert(depth == Depth), (void) depth;
visitor(
storage_policy::template get<value_type>(u.storage),
std::forward<UArgs>(args)...
);
}
template <typename TVisitor, typename... UArgs>
static void visit(
size_type const depth, union_type &u, TVisitor &&visitor,
UArgs &&...args
) {
assert(depth == Depth), (void) depth;
visitor(
storage_policy::template get<value_type>(u.storage),
std::forward<UArgs>(args)...
);
}
static void allocate(
allocator_type const &allocator, size_type const depth, union_type &u
) {
assert(depth == Depth), (void) depth;
storage_policy::template allocate<value_type>(allocator, u.storage);
}
static void deallocate(
allocator_type const &allocator, size_type const depth, union_type &u
) {
assert(depth == Depth), (void) depth;
storage_policy::template deallocate<value_type>(allocator, u.storage);
}
static void copy_construct(
allocator_type const &allocator, size_type const depth,
union_type const &from, union_type &to
) {
assert(depth == Depth), (void) depth;
storage_policy::template construct<value_type>(
allocator, to.storage,
storage_policy::template get<value_type>(from.storage)
);
}
static void destroy(
allocator_type const &allocator, size_type const depth, union_type &u
) {
assert(depth == Depth), (void) depth;
storage_policy::template destroy<value_type>(allocator, u.storage);
}
static void move_over(
size_type const depth, union_type &from, union_type &to
) {
assert(depth == Depth), (void) depth;
storage_policy::template move_over<value_type>(from.storage, to.storage);
}
static bool equals(
size_type const depth, union_type const &lhs, union_type const &rhs
) {
if (depth != Depth) {
assert(depth == end_depth());
return true;
}
return storage_policy::template get<value_type>(lhs.storage)
== storage_policy::template get<value_type>(rhs.storage);
}
static bool less_than(
size_type const depth, union_type const &lhs, union_type const &rhs
) {
if (depth != Depth) {
assert(depth == end_depth());
return false;
}
return storage_policy::template get<value_type>(lhs.storage)
< storage_policy::template get<value_type>(rhs.storage);
}
// templated calls dependent on value_type
// returns end_depth() when U is not found
template <typename U>
constexpr static size_type get_depth() {
return std::is_same<U, value_type>::value ? Depth : end_depth();
}
template <typename U, typename std::enable_if<
std::is_same<U, value_type>::value, int
>::type = 0>
static storage_type const &get(union_type const &u) {
return u.storage;
}
template <typename U, typename std::enable_if<
std::is_same<U, value_type>::value, int
>::type = 0>
static storage_type &get(union_type &u) {
return u.storage;
}
template <typename U, typename... UArgs, typename std::enable_if<
std::is_same<U, value_type>::value, int
>::type = 0>
static value_type &construct(
allocator_type const &allocator,
size_type const depth,
union_type &u,
UArgs &&...args
) {
assert(depth == Depth), (void) depth;
return storage_policy::template construct<value_type>(
allocator, u.storage, std::forward<UArgs>(args)...
);
}
};
// general case
template <
typename TStoragePolicy,
typename TSize, TSize Depth,
typename T, typename Head, typename... Tail
>
struct variadic_union_traits<TStoragePolicy, TSize, Depth, T, Head, Tail...> {
static_assert(std::is_integral<TSize>::value, "TSize must be an integral");
static_assert(!std::is_reference<T>::value, "T can't be a reference");
static_assert(!std::is_const<T>::value, "T can't be const");
using size_type = TSize;
using value_type = T;
using storage_policy = TStoragePolicy;
using allocator_type = typename storage_policy::allocator_type;
using storage_type = typename storage_policy
::template storage_type<value_type>::type;
using head_type = variant_impl::variadic_union_traits<
storage_policy, size_type, Depth, T
>;
using tail_type = variant_impl::variadic_union_traits<
storage_policy, size_type, Depth + 1, Head, Tail...
>;
union union_type {
typename head_type::union_type head;
typename tail_type::union_type tail;
union_type() {}
union_type(union_type const &) {}
union_type(union_type &&) {}
~union_type() {}
};
// calls not dependent on value_type
constexpr static size_type end_depth() { return tail_type::end_depth(); }
template <template <typename> class... TTransforms>
static std::type_info const &type(size_type const depth) {
return depth == Depth
? head_type::template type<TTransforms...>(depth)
: tail_type::template type<TTransforms...>(depth);
}
template <typename TVisitor, typename... UArgs>
static void visit(
size_type const depth, union_type const &u, TVisitor &&visitor,
UArgs &&...args
) {
if (depth == Depth) {
head_type::template visit(
depth, u.head, std::forward<TVisitor>(visitor),
std::forward<UArgs>(args)...
);
} else {
tail_type::template visit(
depth, u.tail, std::forward<TVisitor>(visitor),
std::forward<UArgs>(args)...
);
}
}
template <typename TVisitor, typename... UArgs>
static void visit(
size_type const depth, union_type &u, TVisitor &&visitor,
UArgs &&...args
) {
if (depth == Depth) {
head_type::template visit(
depth, u.head, std::forward<TVisitor>(visitor),
std::forward<UArgs>(args)...
);
} else {
tail_type::template visit(
depth, u.tail, std::forward<TVisitor>(visitor),
std::forward<UArgs>(args)...
);
}
}
static void allocate(
allocator_type const &allocator, size_type const depth, union_type &u
) {
if (depth == Depth) {
head_type::allocate(allocator, depth, u.head);
} else {
tail_type::allocate(allocator, depth, u.tail);
}
}
static void deallocate(
allocator_type const &allocator, size_type const depth, union_type &u
) {
if (depth == Depth) {
head_type::deallocate(allocator, depth, u.head);
} else {
tail_type::deallocate(allocator, depth, u.tail);
}
}
static void copy_construct(
allocator_type const &allocator, size_type const depth,
union_type const &from, union_type &to
) {
if (depth == Depth) {
head_type::copy_construct(allocator, depth, from.head, to.head);
} else {
tail_type::copy_construct(allocator, depth, from.tail, to.tail);
}
}
static void destroy(
allocator_type const &allocator, size_type const depth, union_type &u
) {
if (depth == Depth) {
head_type::destroy(allocator, depth, u.head);
} else {
tail_type::destroy(allocator, depth, u.tail);
}
}
static void move_over(
size_type const depth, union_type &from, union_type &to
) {
if (depth == Depth) {
head_type::move_over(depth, from.head, to.head);
} else {
tail_type::move_over(depth, from.tail, to.tail);
}
}
static bool equals(
size_type const depth, union_type const &lhs, union_type const &rhs
) {
return depth == Depth
? head_type::equals(depth, lhs.head, rhs.head)
: tail_type::equals(depth, lhs.tail, rhs.tail);
}
static bool less_than(
size_type const depth, union_type const &lhs, union_type const &rhs
) {
return depth == Depth
? head_type::less_than(depth, lhs.head, rhs.head)
: tail_type::less_than(depth, lhs.tail, rhs.tail);
}
// templated calls dependent on value_type
// returns end_depth() when U is not found
template <typename U>
constexpr static size_type get_depth() {
return std::is_same<U, value_type>::value
? head_type::template get_depth<U>()
: tail_type::template get_depth<U>();
}
template <typename U, typename std::enable_if<
std::is_same<U, value_type>::value, int
>::type = 0>
static storage_type const &get(union_type const &u) {
return head_type::template get<U>(u.head);
}
template <typename U, typename std::enable_if<
std::is_same<U, value_type>::value, int
>::type = 0>
static storage_type &get(union_type &u) {
return head_type::template get<U>(u.head);
}
template <typename U, typename std::enable_if<
!std::is_same<U, value_type>::value, int
>::type = 0>
static typename storage_policy::template storage_type<U>::type const &get(
union_type const &u
) {
return tail_type::template get<U>(u.tail);
}
template <typename U, typename std::enable_if<
!std::is_same<U, value_type>::value, int
>::type = 0>
static typename storage_policy::template storage_type<U>::type &get(
union_type &u
) {
return tail_type::template get<U>(u.tail);
}
template <typename U, typename... UArgs, typename std::enable_if<
std::is_same<U, value_type>::value, int
>::type = 0>
static U &construct(
allocator_type const &allocator,
size_type const depth,
union_type &u,
UArgs &&...args
) {
return head_type::template construct<U>(
allocator, depth, u.head, std::forward<UArgs>(args)...
);
}
template <typename U, typename... UArgs, typename std::enable_if<
!std::is_same<U, value_type>::value, int
>::type = 0>
static U &construct(
allocator_type const &allocator,
size_type const depth,
union_type &u,
UArgs &&...args
) {
return tail_type::template construct<U>(
allocator, depth, u.tail, std::forward<UArgs>(args)...
);
}
};
template <template <typename...> class UCondition>
class visit_if_visitor {
template <
bool Condition,
typename U,
typename UVisitor,
typename... UArgs,
typename std::enable_if<Condition, int>::type = 0
>
bool visit(
U &&value, UVisitor &&visitor, UArgs &&...args
) const {
visitor(std::forward<U>(value), std::forward<UArgs>(args)...);
return true;
}
template <
bool Condition,
typename U,
typename UVisitor,
typename... UArgs,
typename std::enable_if<!Condition, int>::type = 0
>
bool visit(
U &&, UVisitor &&, UArgs &&...
) const {
return false;
}
public:
template <
typename U, std::size_t Index, typename UTag,
typename UVariant, typename UVisitor, typename... UArgs
>
void operator ()(
indexed_type_tag<U, Index>, UTag &&tag, UVariant &&variant, bool &result,
UVisitor &&visitor, UArgs &&...args
) {
assert(tag == variant.tag()), (void) tag;
assert(Index == variant.tag());
using variant_type = typename std::decay<UVariant>::type;
static_assert(sizeof(variant_type) >= 0, "");
result = visit<UCondition<U>::value>(
variant.variant_type::template get<U>(),
std::forward<UVisitor>(visitor),
std::forward<UArgs>(args)...
);
}
};
template <typename T, bool>
struct nothrow_cp_ctor {
using type = std::is_nothrow_copy_constructible<T>;
};
template <typename T>
struct nothrow_cp_ctor<T, true> {
using type = std::false_type;
};
template <typename T, bool>
struct nothrow_mv_ctor {
using type = std::is_nothrow_move_constructible<T>;
};
template <typename T>
struct nothrow_mv_ctor<T, true> {
using type = std::true_type;
};
template <typename T, bool>
struct nothrow_cp_assign {
using type = std::is_nothrow_copy_assignable<T>;
};
template <typename T>
struct nothrow_cp_assign<T, true> {
using type = std::false_type;
};
template <typename T, bool>
struct nothrow_mv_assign {
using type = std::is_nothrow_move_assignable<T>;
};
template <typename T>
struct nothrow_mv_assign<T, true> {
using type = std::true_type;
};
} // namespace variant_impl {
} // namespace detail {
template <typename, typename...>
struct legacy_variant;
namespace detail {
namespace variant_impl {
template <typename> struct is_variant_impl;
template <typename UStoragePolicy, typename... UArgs>
struct is_variant_impl<legacy_variant<UStoragePolicy, UArgs...>> {};
} // namespace variant_impl {
} // namespace detail {
template <typename T>
using is_variant = is_complete<detail::variant_impl::is_variant_impl<T>>;
/**
* ################
* # 'START HERE' #
* ################
*
* This class represents a variant container which can store values of
* different types, one at a time.
*
* The underlying storage used to hold these values can be fully customized
* by means of a StoragePolicy. A default implementation for this policy is
* provided (fatal::default_storage_policy) which allows the usage of
* either automatic or dynamic allocation to store the values (this can be
* further customized per type by means of an AllocationPolicy, search for
* 'STORAGE POLICY' for more details).
*
* An Allocator must also be provided in order to handle dynamic allocation.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
// TODO: ADD MISSING noexcept DECLARATIONS
template <typename TStoragePolicy, typename... Args>
struct legacy_variant {
using storage_policy = TStoragePolicy;
using allocator_type = typename storage_policy::allocator_type;
using types = type_list<Args...>;
private:
template <typename UStoragePolicy, typename... UArgs>
friend struct legacy_variant;
/**
* This is the control block used by the variant to hold both the allocator
* and the type_tag of the type currently stored in the variant.
*/
struct control_block {
// bits_size is the amount of bits needed to represent the type_tag for
// the given types passed as variadic template parameters (Args...)
constexpr static std::size_t bits_size() {
return most_significant_bit<sizeof...(Args)>::value;
}
using type_tag = smallest_uint_for_value<sizeof...(Args)>;
control_block(allocator_type const &allocator, type_tag storedType):
allocator_(allocator),
tag_(std::move(storedType))
{}
fast_pass<type_tag> storedType() const { return tag_; }
void setStoredType(type_tag tag) { tag_ = std::move(tag); }
allocator_type const &get_allocator() const { return allocator_; }
allocator_type allocator_;
type_tag tag_;
};
template <typename T>
using nothrow_cp_ctor = typename detail::variant_impl::nothrow_cp_ctor<
T, storage_policy::template allocate_dynamically<T>::value
>::type;
template <typename T>
using nothrow_mv_ctor = typename detail::variant_impl::nothrow_mv_ctor<
T, storage_policy::template allocate_dynamically<T>::value
>::type;
template <typename T>
using nothrow_cp_assign = typename detail::variant_impl::nothrow_cp_assign<
T, storage_policy::template allocate_dynamically<T>::value
>::type;
template <typename T>
using nothrow_mv_assign = typename detail::variant_impl::nothrow_mv_assign<
T, storage_policy::template allocate_dynamically<T>::value
>::type;
public:
using type_tag = typename control_block::type_tag;
using traits = detail::variant_impl::variadic_union_traits<
storage_policy,
type_tag, std::numeric_limits<type_tag>::min(),
typename std::decay<Args>::type...
>;
using union_type = typename traits::union_type;
// returns no_tag() when the given type is not part of the variant
template <typename U>
constexpr static type_tag tag() { return traits::template get_depth<U>(); }
// the tag used when the variant is empty
constexpr static type_tag no_tag() { return traits::end_depth(); }
// returns true iff this type can be stored in the variant
template <typename U>
constexpr static bool is_supported() { return tag<U>() != no_tag(); }
legacy_variant() noexcept:
control_(allocator_type(), no_tag())
{}
explicit legacy_variant(allocator_type const &allocator) noexcept:
control_(allocator, no_tag())
{}
legacy_variant(legacy_variant const &other)
noexcept(logical::all<nothrow_cp_ctor<Args>...>::value):
control_(copy_impl(other))
{
static_assert(
storage_policy::is_copyable(),
"copy construction disabled by the variant's policy"
);
}
legacy_variant(legacy_variant &&other)
noexcept(logical::all<nothrow_mv_ctor<Args>...>::value):
control_(move_impl(std::move(other)))
{}
template <typename U>
explicit legacy_variant(allocator_type const &allocator, U &&value):
control_(allocator, no_tag())
{
set<typename std::decay<U>::type>(std::forward<U>(value));
}
template <
typename U,
typename = safe_overload<legacy_variant, U>,
typename std::enable_if<
!std::is_convertible<U&&, allocator_type const &>::value, int
>::type = 0
>
explicit legacy_variant(U &&value) : control_(allocator_type(), no_tag()) {
set(std::forward<U>(value));
}
template <typename... UArgs>
legacy_variant(legacy_variant<storage_policy, UArgs...> const &other):
legacy_variant(other.control_.get_allocator())
{
other.visit(copy_variant_visitor<true, false>(), *this);
}
template <typename UStoragePolicy, typename... UArgs>
legacy_variant(
allocator_type const &allocator,
legacy_variant<UStoragePolicy, UArgs...> const &other
):
legacy_variant(allocator)
{
other.visit(copy_variant_visitor<true, false>(), *this);
}
~legacy_variant() { unset_impl(); }
/**
* The unchecked_get won't perform type checks. The user must ensure the
* requested type matches the one stored in the variant. Provided for
* performance sensitive scenarios.
*/
template <typename U>
U const &unchecked_get() const & {
return storage_policy::template get<U>(
traits::template get<U>(union_)
);
}
template <typename U>
U &unchecked_get() & {
return storage_policy::template get<U>(
traits::template get<U>(union_)
);
}
template <typename U>
U &&unchecked_get() && {
return std::move(storage_policy::template get<U>(
traits::template get<U>(union_)
));
}
template <typename U>
U const &get() const & {
if (tag<U>() != control_.storedType()) {
throw std::invalid_argument(
"requested type doesn't match the one contained in the variant"
);
}
return storage_policy::template get<U>(
traits::template get<U>(union_)
);
}
template <typename U>
U &get() & {
if (tag<U>() != control_.storedType()) {
throw std::invalid_argument(
"requested type doesn't match the one contained in the variant"
);
}
return storage_policy::template get<U>(
traits::template get<U>(union_)
);
}
template <typename U>
U &&get() && {
if (tag<U>() != control_.storedType()) {
throw std::invalid_argument(
"requested type doesn't match the one contained in the variant"
);
}
return std::move(storage_policy::template get<U>(
traits::template get<U>(union_)
));
}
template <typename U>
U const *try_get() const & {
if (tag<U>() != control_.storedType()) {
return nullptr;
}
return std::addressof(
storage_policy::template get<U>(
traits::template get<U>(union_)
)
);
}
template <typename U>
U *try_get() & {
if (tag<U>() != control_.storedType()) {
return nullptr;
}
return std::addressof(
storage_policy::template get<U>(
traits::template get<U>(union_)
)
);
}
template <typename U>
U *try_get() && = delete;
template <
template <typename...> class UCondition,
typename UVisitor, typename... UArgs
>
bool visit_if(UVisitor &&visitor, UArgs &&...args) const {
bool result = false;
return types::template binary_search<index_value_comparer>::exact(
control_.storedType(),
detail::variant_impl::visit_if_visitor<UCondition>(),
*this, result,
std::forward<UVisitor>(visitor),
std::forward<UArgs>(args)...
) && result;
}
template <
template <typename...> class UCondition,
typename UVisitor, typename... UArgs
>
bool visit_if(UVisitor &&visitor, UArgs &&...args) {
bool result = false;
return types::template binary_search<index_value_comparer>::exact(
control_.storedType(),
detail::variant_impl::visit_if_visitor<UCondition>(),
*this, result,
std::forward<UVisitor>(visitor),
std::forward<UArgs>(args)...
) && result;
}
template <typename TVisitor, typename... UArgs>
bool visit(TVisitor &&visitor, UArgs &&...args) const {
auto const storedType = control_.storedType();
if (storedType == no_tag()) {
return false;
}
traits::template visit(
storedType, union_, std::forward<TVisitor>(visitor),
std::forward<UArgs>(args)...
);
return true;
}
template <typename TVisitor, typename... UArgs>
bool visit(TVisitor &&visitor, UArgs &&...args) {
auto const storedType = control_.storedType();
if (storedType == no_tag()) {
return false;
}
traits::template visit(
storedType, union_, std::forward<TVisitor>(visitor),
std::forward<UArgs>(args)...
);
return true;
}
template <typename U, typename std::enable_if<
is_supported<typename std::decay<U>::type>(), int
>::type = 0>
bool try_set(U &&value) {
*this = std::forward<U>(value);
return true;
}
template <typename U, typename std::enable_if<
!is_supported<typename std::decay<U>::type>(), int
>::type = 0>
bool try_set(U &&) {
return false;
}
template <typename UCallable, typename... UArgs, typename std::enable_if<
is_supported<
typename std::decay<
typename std::result_of<UCallable&(UArgs...)>::type
>::type
>(), int
>::type = 0>
bool set_result_of(UCallable &&callable, UArgs &&...args) {
*this = callable(std::forward<UArgs>(args)...);
return true;
}
template <typename UCallable, typename... UArgs, typename std::enable_if<
!is_supported<
typename std::decay<
typename std::result_of<UCallable&(UArgs...)>::type
>::type
>(), int
>::type = 0>
bool set_result_of(UCallable &&callable, UArgs &&...args) {
callable(std::forward<UArgs>(args)...);
return false;
}
template <typename U>
U &set(U const &value) & {
return set_impl<typename std::decay<U>::type>(value);
}
template <typename U, typename = enable_when::movable<U>>
U &set(U &&value) & {
return set_impl<typename std::decay<U>::type>(
std::forward<U>(value)
);
}
template <typename U>
U &&set(U const &value) && {
return std::move(set_impl<typename std::decay<U>::type>(value));
}
template <typename U, typename = enable_when::movable<U>>
U &&set(U &&value) && {
return std::move(set_impl<typename std::decay<U>::type>(
std::forward<U>(value)
));
}
template <typename U, typename... UArgs>
U &emplace(UArgs &&...args) & {
return set_impl<typename std::decay<U>::type>(
std::forward<UArgs>(args)...
);
}
template <typename U, typename... UArgs>
U &&emplace(UArgs &&...args) && {
return std::move(set_impl<typename std::decay<U>::type>(
std::forward<UArgs>(args)...
));
}
void clear() { unset_impl(); }
bool empty() const { return control_.storedType() == no_tag(); }
void swap(legacy_variant &other) {
if (this == std::addressof(other)) {
return;
}
auto const storedType = control_.storedType();
auto const otherStoredType = other.control_.storedType();
union_type tmp;
if (storedType != no_tag()) {
traits::move_over(storedType, union_, tmp);
}
if (otherStoredType != no_tag()) {
traits::move_over(otherStoredType, other.union_, union_);
}
if (storedType != no_tag()) {
traits::move_over(storedType, tmp, other.union_);
}
std::swap(control_, other.control_);
}
type_tag tag() const { return control_.storedType(); }
template <typename U>
bool is_of() const { return tag() == tag<U>(); }
template <typename U>
using tag_for = std::integral_constant<type_tag, legacy_variant::tag<U>()>;
template <typename UList>
using tags_for = typename UList::template transform<tag_for>;
template <typename... U>
bool is_any_of() const {
return tags_for<type_list<U...>>::template binary_search<>::exact(
tag(), fn::no_op()
);
}
template <typename UList>
bool is_any_from() const {
return tags_for<UList>::template binary_search<>::exact(tag(), fn::no_op());
}
// for compatibility with boost::variant
typename std::enable_if<
traits::end_depth() <= std::numeric_limits<int>::max(), int
>::type which() const {
return static_cast<int>(tag());
}
// for compatibility with boost::variant
template <template <typename> class... TTransforms>
std::type_info const &type() const {
return traits::template type<TTransforms...>(control_.storedType());
}
allocator_type get_allocator() const { return control_.get_allocator(); }
legacy_variant &operator =(legacy_variant const &other)
noexcept(
logical::all<std::true_type, nothrow_cp_assign<Args>...>::value
)
{
static_assert(
storage_policy::is_copyable(),
"copy assignment disabled by the variant's policy"
);
if (this != std::addressof(other)) {
unset_impl();
control_ = copy_impl(other);
}
return *this;
}
legacy_variant &operator =(legacy_variant &other)
noexcept(logical::all<nothrow_cp_assign<Args>...>::value)
{
return (*this = static_cast<legacy_variant const &>(other));
}
legacy_variant &operator =(legacy_variant &&other)
noexcept(logical::all<nothrow_mv_assign<Args>...>::value)
{
if (this != std::addressof(other)) {
unset_impl();
control_ = move_impl(std::move(other));
}
return *this;
}
template <typename U>
U &operator =(U const &value) & {
return set(value);
}
template <
typename U,
typename = safe_overload<legacy_variant, U>,
typename = enable_when::movable<U>
>
U &operator =(U &&value) & {
return set(std::forward<U>(value));
}
template <typename U>
U &&operator =(U const &value) && {
return std::move(set(value));
}
template <
typename U,
typename = safe_overload<legacy_variant, U>,
typename = enable_when::movable<U>
>
U &&operator =(U &&value) && {
return std::move(set(std::forward<U>(value)));
}
template <typename UStoragePolicy, typename... UArgs>
legacy_variant &operator =(
legacy_variant<UStoragePolicy, UArgs...> const &other
) {
if (!other.visit(copy_variant_visitor<false, true>(), *this)) {
clear();
}
return *this;
}
template <typename UStoragePolicy, typename... UArgs>
legacy_variant &operator =(legacy_variant<UStoragePolicy, UArgs...> &other) {
if (!other.visit(copy_variant_visitor<false, true>(), *this)) {
clear();
}
return *this;
}
template <typename... UArgs>
legacy_variant &operator =(legacy_variant<storage_policy, UArgs...> &&other) {
if (control_.get_allocator() == other.control_.get_allocator()) {
if (!other.visit(copy_variant_visitor<false, true>(), *this)) {
clear();
}
other.clear();
} else {
// can't move when different storage policies
// or different allocators are employed
if (storage_policy::is_copyable()) {
if (!other.visit(copy_variant_visitor<false, true>(), *this)) {
clear();
}
other.clear();
} else {
throw std::invalid_argument(
"can't move values allocated by different allocators"
" and storage policy doesn't allow copies"
);
}
}
return *this;
}
template <typename UStoragePolicy, typename... UArgs>
legacy_variant &operator =(legacy_variant<UStoragePolicy, UArgs...> &&other) {
if (!other.visit(copy_variant_visitor<false, true>(), *this)) {
clear();
}
other.clear();
return *this;
}
bool operator ==(legacy_variant const &other) const {
auto const storedType = control_.storedType();
if (storedType != other.control_.storedType()) {
return false;
}
return traits::equals(storedType, union_, other.union_);
}
bool operator <(legacy_variant const &other) const {
auto const storedType = control_.storedType();
auto const otherStoredType = other.control_.storedType();
if (storedType != otherStoredType) {
return storedType < otherStoredType;
}
return traits::less_than(storedType, union_, other.union_);
}
bool operator !=(legacy_variant const &other) const {
return !(*this == other);
}
bool operator >(legacy_variant const &other) const {
return other < *this;
}
bool operator <=(legacy_variant const &other) const {
return !(other < *this);
}
bool operator >=(legacy_variant const &other) const {
return !(*this < other);
}
private:
union_type union_;
control_block control_;
template <typename U, typename... UArgs>
U &set_impl(UArgs &&...args) {
auto const storedType = control_.storedType();
auto const &allocator = control_.get_allocator();
static_assert((is_supported<U>()), "can't set an unsupported type");
if (storedType == tag<U>()) {
assert(storedType != no_tag());
auto &storage = traits::template get<U>(union_);
storage_policy::template destroy<U>(allocator, storage);
try {
return storage_policy::template construct<U>(
allocator, storage, std::forward<UArgs>(args)...
);
} catch(...) {
control_.setStoredType(no_tag());
storage_policy::template deallocate<U>(allocator, storage);
throw;
}
}
unset_impl();
traits::allocate(allocator, tag<U>(), union_);
try {
auto &result = traits::template construct<U>(
allocator, tag<U>(), union_, std::forward<UArgs>(args)...
);
control_.setStoredType(tag<U>());
return result;
} catch(...) {
traits::deallocate(allocator, tag<U>(), union_);
throw;
}
}
void unset_impl() {
auto const storedType = control_.storedType();
auto const &allocator = control_.get_allocator();
if (storedType == no_tag()) {
return;
}
traits::destroy(allocator, storedType, union_);
traits::deallocate(allocator, storedType, union_);
control_.setStoredType(no_tag());
}
// assumes already unset
control_block copy_impl(legacy_variant const &other) {
auto const otherStoredType = other.control_.storedType();
auto otherAllocator = other.control_.get_allocator();
if (otherStoredType == no_tag()) {
return other.control_;
}
traits::allocate(otherAllocator, otherStoredType, union_);
try {
traits::copy_construct(
otherAllocator, otherStoredType, other.union_, union_
);
} catch(...) {
traits::deallocate(otherAllocator, otherStoredType, union_);
throw;
}
return other.control_;
}
// assumes already unset
control_block move_impl(legacy_variant &&other) {
auto const otherStoredType = other.control_.storedType();
if (otherStoredType != no_tag()) {
traits::move_over(otherStoredType, other.union_, union_);
}
control_block control(
other.control_.get_allocator(),
other.control_.storedType()
);
other.control_.setStoredType(no_tag());
return control;
}
template <bool ThrowIfUnsupported, bool ClearIfUnsupported>
class copy_variant_visitor {
template <typename U, bool>
struct setter {
static void set(legacy_variant &v, U const &value) { v.set(value); }
};
template <typename U>
struct setter<U, false> {
static void set(legacy_variant &v, U const &) {
if (ClearIfUnsupported) {
v.clear();
}
if (ThrowIfUnsupported) {
throw std::invalid_argument("type not supported");
}
}
};
public:
template <typename U>
void operator ()(U const &value, legacy_variant &v) {
static_assert(
storage_policy::is_copyable(),
"copy disabled by the variant's policy"
);
using type = typename std::decay<U>::type;
setter<type, legacy_variant::template is_supported<type>()>::set(
v, value
);
}
};
};
template <typename TStoragePolicy, typename... Args>
using variant = legacy_variant<TStoragePolicy, Args...>;
template <typename... Args>
using legacy_default_variant = legacy_variant<
legacy_storage_policy<>,
Args...
>;
template <typename... Args>
using default_variant = legacy_default_variant<Args...>;
template <typename... Args>
using auto_variant = variant<
default_storage_policy<
std::allocator<void>,
automatic_allocation_policy
>,
Args...
>;
template <typename Allocator, typename... Args>
using dynamic_variant = variant<
default_storage_policy<Allocator, dynamic_allocation_policy>,
Args...
>;
template <typename... Args>
using default_dynamic_variant = dynamic_variant<std::allocator<void>, Args...>;
/**
* Wraps non-derived visitors that return a value on operator()
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <typename TVisitor, typename TResult>
struct visitor_wrapper {
using visitor_type = TVisitor;
using result_type = TResult;
explicit visitor_wrapper(visitor_type &visitor):
visitor_(visitor)
{}
template <typename... UArgs>
void operator()(UArgs &&...args) {
result_.emplace(visitor_(std::forward<UArgs>(args)...));
}
bool has_value() const { return !result_.empty(); }
fast_pass<result_type> value() const { return *result_; }
result_type &value() { return result_.ref(); }
private:
visitor_type &visitor_;
optional<result_type> result_;
};
/**
* A handy function to build visitor_wrapper instances given a visitor
* whose operator() returns a value
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <typename result_type, typename visitor_type>
visitor_wrapper<visitor_type const, result_type> wrap_visitor(
visitor_type const &visitor
) {
return visitor_wrapper<visitor_type const, result_type>(visitor);
}
template <typename result_type, typename visitor_type>
visitor_wrapper<visitor_type, result_type> wrap_visitor(
visitor_type &visitor
) {
return visitor_wrapper<visitor_type, result_type>(visitor);
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function throws std::logic_error.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type visit(
variant<TStoragePolicy, Args...> const &variant,
visitor_type &&visitor,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
if (!wrapper.has_value()) {
throw std::logic_error("there's no value returned by the visitor");
}
return std::move(wrapper.value());
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function throws std::logic_error.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type visit(
variant<TStoragePolicy, Args...> &variant,
visitor_type &&visitor,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
if (!wrapper.has_value()) {
throw std::logic_error("there's no value returned by the visitor");
}
return std::move(wrapper.value());
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function throws std::logic_error.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type visit(
variant<TStoragePolicy, Args...> &&variant,
visitor_type &&visitor,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
if (!wrapper.has_value()) {
throw std::logic_error("there's no value returned by the visitor");
}
return std::move(wrapper.value());
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function throws std::logic_error.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type legacy_visit(
variant<TStoragePolicy, Args...> const &variant,
visitor_type &&visitor,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
if (!wrapper.has_value()) {
throw std::logic_error("there's no value returned by the visitor");
}
return std::move(wrapper.value());
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function throws std::logic_error.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type legacy_visit(
variant<TStoragePolicy, Args...> &variant,
visitor_type &&visitor,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
if (!wrapper.has_value()) {
throw std::logic_error("there's no value returned by the visitor");
}
return std::move(wrapper.value());
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function throws std::logic_error.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type legacy_visit(
variant<TStoragePolicy, Args...> &&variant,
visitor_type &&visitor,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
if (!wrapper.has_value()) {
throw std::logic_error("there's no value returned by the visitor");
}
return std::move(wrapper.value());
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function returns `defaultValue`.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type visit_def(
variant<TStoragePolicy, Args...> const &variant,
visitor_type &&visitor,
result_type defaultValue,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
return wrapper.has_value()
? std::move(wrapper.value())
: defaultValue;
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function returns `defaultValue`.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type visit_def(
variant<TStoragePolicy, Args...> &variant,
visitor_type &&visitor,
result_type defaultValue,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
return wrapper.has_value()
? std::move(wrapper.value())
: defaultValue;
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function returns `defaultValue`.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type visit_def(
variant<TStoragePolicy, Args...> &&variant,
visitor_type &&visitor,
result_type defaultValue,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
return wrapper.has_value()
? std::move(wrapper.value())
: defaultValue;
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function returns `defaultValue`.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type legacy_visit_def(
variant<TStoragePolicy, Args...> const &variant,
visitor_type &&visitor,
result_type defaultValue,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
return wrapper.has_value()
? std::move(wrapper.value())
: defaultValue;
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function returns `defaultValue`.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type legacy_visit_def(
variant<TStoragePolicy, Args...> &variant,
visitor_type &&visitor,
result_type defaultValue,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
return wrapper.has_value()
? std::move(wrapper.value())
: defaultValue;
}
/**
* Convenience function to visit a variant using a visitor that returns values.
*
* If the variant holds no values (the visitor is not actually called), this
* function returns `defaultValue`.
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
template <
typename result_type, typename visitor_type,
typename TStoragePolicy, typename... Args,
typename... UArgs
>
result_type legacy_visit_def(
variant<TStoragePolicy, Args...> &&variant,
visitor_type &&visitor,
result_type defaultValue,
UArgs &&...args
) {
auto wrapper = wrap_visitor<result_type>(visitor);
variant.visit(wrapper, std::forward<UArgs>(args)...);
return wrapper.has_value()
? std::move(wrapper.value())
: defaultValue;
}
} // namespace fatal {
namespace std {
template <typename TStoragePolicy, typename... Args>
struct hash<fatal::variant<TStoragePolicy, Args...>> {
using result_type = std::size_t;
result_type operator ()(
fatal::variant<TStoragePolicy, Args...> const &v
) const {
hash_visitor visitor(std::hash<decltype(v.tag())>()(v.tag()));
v.visit(visitor);
return visitor.result;
}
private:
struct hash_visitor {
explicit hash_visitor(result_type seed): result(std::move(seed)) {}
template <typename U>
void operator ()(U const &value) {
result ^= std::hash<typename std::decay<U>::type>()(value);
}
result_type result;
};
};
} // namespace std {
FATAL_DIAGNOSTIC_POP
#endif // FATAL_INCLUDE_fatal_container_legacy_variant_h