blob: d506f8e4a4efb6adba2e8e4a44bf9c14efbe9b56 [file] [log] [blame]
// Copyright (C) 2020 The Android Open Source Project
//
// 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 "dctv.h"
#include <assert.h>
#include <algorithm>
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
namespace dctv {
// Very simple switch-based variant-ish object, unlike the libstdc++
// and c++ variant implementations, is simple enough that the compiler
// can optimize it.
//
// Unlike std::variant, we require that all contained types are
// nothrow-moveable and nothrow-constructible. We never construct in
// place, so we can never be valueless by exception.
template<typename T>
struct TypeNotInSimpleVariant;
template<std::size_t Index, typename Needle, typename... Haystack>
struct index_of_v_impl : TypeNotInSimpleVariant<Needle> {};
struct monostate {};
template<typename Needle, typename... Haystack>
static constexpr std::size_t index_of_v =
index_of_v_impl<0, Needle, Haystack...>::value;
template<std::size_t N, typename enable = void>
struct ArmNumberBase;
template<typename... T>
struct SimpleVariant : ArmNumberBase<sizeof...(T)> {
private:
using Base = ArmNumberBase<sizeof...(T)>;
using ArmsTuple = typename std::tuple<T...>;
template<std::size_t Index>
using ArmAtIndex = typename std::tuple_element_t<Index, ArmsTuple>;
using FirstType = ArmAtIndex<0>;
static constexpr bool can_default_construct =
std::is_default_constructible_v<FirstType>;
static constexpr bool is_nothrow_copy_constructible =
std::conjunction_v<std::is_nothrow_copy_constructible<T>...>;
static constexpr bool is_nothrow_copy_assignable =
std::conjunction_v<std::is_nothrow_copy_assignable<T>...>;
public:
static_assert(
std::conjunction_v<std::is_nothrow_destructible<T>...>);
static_assert(
std::conjunction_v<std::is_nothrow_move_constructible<T>...>);
static_assert(
std::conjunction_v<std::is_nothrow_move_assignable<T>...>);
template<typename R> inline SimpleVariant(R r) noexcept; // NOLINT
inline SimpleVariant(SimpleVariant&& other) noexcept;
inline SimpleVariant& operator=(SimpleVariant&& other) noexcept;
inline SimpleVariant(const SimpleVariant& other)
noexcept(is_nothrow_copy_constructible);
inline SimpleVariant& operator=(const SimpleVariant& other)
noexcept(is_nothrow_copy_assignable);
inline ~SimpleVariant() noexcept;
inline bool operator==(const SimpleVariant& other) const;
inline bool operator!=(const SimpleVariant& other) const;
template<typename Dummy = void>
inline SimpleVariant( // NOLINT
typename std::enable_if_t<can_default_construct, Dummy>* = 0)
: SimpleVariant(make_default_type())
{}
template<typename Functor>
inline auto visit(Functor&& functor) &;
template<typename Functor>
inline auto visit(Functor&& functor) &&;
template<typename Functor>
inline auto visit(Functor&& functor) const &;
template<typename Functor>
inline auto visit(Functor&& functor) const &&;
template<typename R> inline R* get_if() noexcept;
template<typename R> inline const R* get_if() const noexcept;
private:
static constexpr std::size_t needed_size = std::max({sizeof (T)...});
static constexpr std::size_t needed_alignment = std::max({alignof(T)...});
using Buf = std::aligned_storage_t<needed_size, needed_alignment>;
template<std::size_t Index,
typename R,
typename This,
typename Functor>
inline static R visit_impl(This&& this_, Functor&& functor);
static inline FirstType make_default_type();
void destroy() noexcept;
Buf storage;
};
// Do signature checking, but only in debug builds. The virtual-ness
// doesn't affect the actual called code because VARIANT_FORWARD
// always does a non-virtual call to a concrete function, even in
// debug builds.
#if !defined(NDEBUG)
# define VARIANT_WHEN_DEBUG(x) x
#else
# define VARIANT_WHEN_DEBUG(x)
#endif
#define VARIANT_VIRTUAL VARIANT_WHEN_DEBUG(virtual)
#define VARIANT_OVERRIDE VARIANT_WHEN_DEBUG(override)
#define VARIANT_ABSTRACT VARIANT_WHEN_DEBUG(= 0)
#define VARIANT_FORWARD(name) \
template<typename... Args> \
inline auto name(Args&&... args) { \
return this->visit([&](auto& value) { \
using Variant = std::decay_t<decltype(value)>; \
VARIANT_WHEN_DEBUG( \
static_assert(std::is_base_of_v<variant_base, Variant>)); \
return value.Variant::name(std::forward<Args>(args)...); \
}); \
} \
template<typename... Args> \
inline auto name(Args&&... args) const { \
return this->visit([&](auto& value) { \
using Variant = std::decay_t<decltype(value)>; \
VARIANT_WHEN_DEBUG( \
static_assert(std::is_base_of_v<variant_base, Variant>)); \
return value.Variant::name(std::forward<Args>(args)...); \
}); \
}
void test_simple_variant();
} // namespace dctv
#include "simple_variant-inl.h"