blob: f91a4a24abf0177409f852e024861fa3627a81e9 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef LIB_FIT_VARIANT_H_
#define LIB_FIT_VARIANT_H_
#include <assert.h>
#include <new>
#include <type_traits>
#include <utility>
namespace fit {
namespace internal {
// This is a very basic partial implementation of |std::variant| which
// is compatible with C++ 14. In its current state, it only implements
// enough of the API for internal usage. Transforming this into a more
// complete and precise implementation of |std::variant| is left as an
// exercise for the reader. ;)
//
// Some key differences:
// - always requires first type to be monostate
// - always default constructible
// - no relational operators
// - no visitors
// - no find by type, only find by index
// - no exception support
// - simplified get/set methods
// Unit type.
struct monostate final {
constexpr bool operator==(const monostate& other) const { return true; }
constexpr bool operator!=(const monostate& other) const { return false; }
};
// Tag for requesting in-place initialization of a variant alternative by index.
template <size_t index>
struct in_place_index_t final {};
#ifdef __cpp_inline_variables
// Inline variables are only available on C++ 17 and beyond.
template <size_t index>
inline constexpr in_place_index_t<index> in_place_index{};
#else
// On C++ 14 we need to provide storage for the variable so we define
// |in_place_index| as a reference instead.
template <size_t index>
struct in_place_index_holder {
static constexpr in_place_index_t<index> instance{};
};
template <size_t index>
constexpr in_place_index_t<index> in_place_index_holder<index>::instance;
template <size_t index>
static constexpr const in_place_index_t<index>& in_place_index =
in_place_index_holder<index>::instance;
#endif // __cpp_inline_variables
// Stores the contents of the variant as a recursively nested union
// of alternatives. Conceptually it might be simpler to use
// std::in_place_storage<> but we would lose the ability to treat the
// storage as a literal type.
template <typename... Ts>
union variant_storage final {
static constexpr bool copy_construct_supported = true;
static constexpr bool move_construct_supported = true;
void copy_construct_from(size_t index, const variant_storage& other) {}
void move_construct_from(size_t index, variant_storage&& other) {}
void destroy(size_t index) {}
void swap(size_t index, variant_storage& other) {}
};
template <typename T0, typename... Ts>
union variant_storage<T0, Ts...> final {
static constexpr bool copy_construct_supported =
std::is_copy_constructible<T0>::value &&
variant_storage<Ts...>::copy_construct_supported;
static constexpr bool move_construct_supported =
std::is_move_constructible<T0>::value &&
variant_storage<Ts...>::move_construct_supported;
constexpr variant_storage() = default;
template <typename... Args>
explicit constexpr variant_storage(in_place_index_t<0>, Args&&... args)
: alt(std::forward<Args>(args)...) {}
template <size_t index, typename... Args>
explicit constexpr variant_storage(in_place_index_t<index>, Args&&... args)
: rest(in_place_index<index - 1>, std::forward<Args>(args)...) {}
constexpr T0& get(in_place_index_t<0>) { return alt; }
constexpr const T0& get(in_place_index_t<0>) const { return alt; }
template <size_t index>
constexpr auto& get(in_place_index_t<index>) {
return rest.get(in_place_index<index - 1>);
}
template <size_t index>
constexpr const auto& get(in_place_index_t<index>) const {
return rest.get(in_place_index<index - 1>);
}
template <typename... Args>
auto& emplace(in_place_index_t<0>, Args&&... args) {
new (&alt) T0(std::forward<Args>(args)...);
return alt;
}
template <size_t index, typename... Args>
auto& emplace(in_place_index_t<index>, Args&&... args) {
return rest.emplace(in_place_index<index - 1>,
std::forward<Args>(args)...);
}
void copy_construct_from(size_t index, const variant_storage& other) {
if (index == 0) {
new (&alt) T0(other.alt);
} else {
rest.copy_construct_from(index - 1, other.rest);
}
}
void move_construct_from(size_t index, variant_storage&& other) {
if (index == 0) {
new (&alt) T0(std::move(other.alt));
} else {
rest.move_construct_from(index - 1, std::move(other.rest));
}
}
void destroy(size_t index) {
if (index == 0) {
alt.~T0();
} else {
rest.destroy(index - 1);
}
}
void swap(size_t index, variant_storage& other) {
using std::swap;
if (index == 0) {
swap(alt, other.alt);
} else {
rest.swap(index - 1, other.rest);
}
}
T0 alt;
variant_storage<Ts...> rest;
};
// Holds the index and storage for a variant with a trivial destructor.
template <typename... Ts>
class variant_base_impl_trivial;
template <typename... Ts>
class variant_base_impl_trivial<monostate, Ts...> {
public:
constexpr variant_base_impl_trivial()
: index_(0),
storage_(in_place_index<0>, monostate{}) {}
template <size_t index, typename... Args>
explicit constexpr variant_base_impl_trivial(
in_place_index_t<index>, Args&&... args)
: index_(index),
storage_(in_place_index<index>, std::forward<Args>(args)...) {}
// Used by emplace.
void destroy() {}
protected:
size_t index_;
variant_storage<monostate, Ts...> storage_;
};
// Holds the index and storage for a variant with a non-trivial destructor.
template <typename... Ts>
class variant_base_impl_non_trivial;
template <typename... Ts>
class variant_base_impl_non_trivial<monostate, Ts...> {
public:
constexpr variant_base_impl_non_trivial()
: index_(0),
storage_(in_place_index<0>, monostate{}) {}
template <size_t index, typename... Args>
explicit constexpr variant_base_impl_non_trivial(
in_place_index_t<index>, Args&&... args)
: index_(index),
storage_(in_place_index<index>, std::forward<Args>(args)...) {}
~variant_base_impl_non_trivial() {
destroy();
}
// Used by emplace and by the destructor.
void destroy() {
storage_.destroy(index_);
}
protected:
size_t index_;
union {
variant_storage<monostate, Ts...> storage_;
};
};
// Selects an appropriate variant base class depending on whether
// its destructor is trivial or non-trivial.
template <typename... Ts>
using variant_base_impl =
std::conditional_t<
std::is_destructible<
variant_base_impl_trivial<Ts...>>::value,
variant_base_impl_trivial<Ts...>,
variant_base_impl_non_trivial<Ts...>>;
// Implements non-trivial move-construction and move-assignment.
template <typename... Ts>
class variant_move_impl_non_trivial : protected variant_base_impl<Ts...> {
using base = variant_base_impl<Ts...>;
public:
using base::base;
variant_move_impl_non_trivial(
const variant_move_impl_non_trivial& other) = default;
variant_move_impl_non_trivial(
variant_move_impl_non_trivial&& other) {
index_ = other.index_;
storage_.move_construct_from(index_, std::move(other.storage_));
}
variant_move_impl_non_trivial& operator=(
const variant_move_impl_non_trivial& other) = default;
variant_move_impl_non_trivial& operator=(
variant_move_impl_non_trivial&& other) {
if (&other == this)
return *this;
storage_.destroy(index_);
index_ = other.index_;
storage_.move_construct_from(index_, std::move(other.storage_));
return *this;
}
protected:
using base::index_;
using base::storage_;
};
// Selects an appropriate variant base class for moving.
template <typename... Ts>
using variant_move_impl =
std::conditional_t<
(std::is_move_constructible<variant_base_impl<Ts...>>::value &&
std::is_move_assignable<variant_base_impl<Ts...>>::value) ||
!variant_storage<Ts...>::move_construct_supported,
variant_base_impl<Ts...>,
variant_move_impl_non_trivial<Ts...>>;
// Implements non-trivial copy-construction and copy-assignment.
template <typename... Ts>
class variant_copy_impl_non_trivial : protected variant_move_impl<Ts...> {
using base = variant_move_impl<Ts...>;
public:
using base::base;
variant_copy_impl_non_trivial(
const variant_copy_impl_non_trivial& other) {
index_ = other.index_;
storage_.copy_construct_from(index_, other.storage_);
}
variant_copy_impl_non_trivial(
variant_copy_impl_non_trivial&&) = default;
variant_copy_impl_non_trivial& operator=(
const variant_copy_impl_non_trivial& other) {
if (&other == this)
return *this;
storage_.destroy(index_);
index_ = other.index_;
storage_.copy_construct_from(index_, other.storage_);
return *this;
}
variant_copy_impl_non_trivial& operator=(
variant_copy_impl_non_trivial&&) = default;
protected:
using base::index_;
using base::storage_;
};
// Selects an appropriate variant base class for copying.
// Use the base impl if the type is trivially
template <typename... Ts>
using variant_copy_impl =
std::conditional_t<
(std::is_copy_constructible<variant_move_impl<Ts...>>::value &&
std::is_copy_assignable<variant_move_impl<Ts...>>::value) ||
!variant_storage<Ts...>::copy_construct_supported,
variant_move_impl<Ts...>,
variant_copy_impl_non_trivial<Ts...>>;
// Actual variant type.
template <typename... Ts>
class variant : private variant_copy_impl<Ts...> {
using base = variant_copy_impl<Ts...>;
public:
constexpr variant() = default;
template <size_t index, typename... Args>
explicit constexpr variant(in_place_index_t<index> i, Args&&... args)
: base(i, std::forward<Args>(args)...) {}
variant(const variant&) = default;
variant(variant&&) = default;
~variant() = default;
variant& operator=(const variant&) = default;
variant& operator=(variant&&) = default;
constexpr size_t index() const { return index_; }
template <size_t index>
constexpr auto& get() {
assert(index_ == index);
return storage_.get(in_place_index<index>);
}
template <size_t index>
constexpr const auto& get() const {
assert(index_ == index);
return storage_.get(in_place_index<index>);
}
template <size_t index, typename... Args>
auto& emplace(Args&&... args) {
this->destroy();
index_ = index;
return storage_.emplace(in_place_index<index>,
std::forward<Args>(args)...);
}
void swap(variant& other) {
using std::swap;
if (&other == this)
return;
if (index_ == other.index_) {
storage_.swap(index_, other.storage_);
} else {
variant temp(std::move(*this));
*this = std::move(other);
other = std::move(temp);
}
}
private:
using base::index_;
using base::storage_;
};
// Swaps variants.
template <typename... Ts>
void swap(variant<Ts...>& a, variant<Ts...>& b) {
a.swap(b);
}
// Gets the type of a variant alternative with the given index.
template <size_t index, typename Variant>
struct variant_alternative;
template <size_t index, typename T0, typename... Ts>
struct variant_alternative<index, variant<T0, Ts...>>
: variant_alternative<index - 1, variant<Ts...>> {};
template <typename T0, typename... Ts>
struct variant_alternative<0, variant<T0, Ts...>> {
using type = T0;
};
template <size_t index, typename Variant>
using variant_alternative_t = typename variant_alternative<index, Variant>::type;
} // namespace internal
} // namespace fit
#endif // LIB_FIT_VARIANT_H_