blob: a95e8dca06237a0bfdf34bd45ca0fcff249a55df [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* All rights reserved.
*
* 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 <lib/core/CHIPCore.h>
#include <algorithm>
#include <new>
#include <tuple>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <lib/core/InPlace.h>
namespace chip {
namespace VariantInternal {
template <std::size_t Index, typename... Ts>
struct VariantCurry;
template <std::size_t Index, typename T, typename... Ts>
struct VariantCurry<Index, T, Ts...>
{
inline static void Destroy(std::size_t id, void * data)
{
if (id == Index)
reinterpret_cast<T *>(data)->~T();
else
VariantCurry<Index + 1, Ts...>::Destroy(id, data);
}
inline static void Move(std::size_t that_t, void * that_v, void * this_v)
{
if (that_t == Index)
new (this_v) T(std::move(*reinterpret_cast<T *>(that_v)));
else
VariantCurry<Index + 1, Ts...>::Move(that_t, that_v, this_v);
}
inline static void Copy(std::size_t that_t, const void * that_v, void * this_v)
{
if (that_t == Index)
{
new (this_v) T(*reinterpret_cast<const T *>(that_v));
}
else
{
VariantCurry<Index + 1, Ts...>::Copy(that_t, that_v, this_v);
}
}
inline static bool Equal(std::size_t type_t, const void * that_v, const void * this_v)
{
if (type_t == Index)
{
return *reinterpret_cast<const T *>(this_v) == *reinterpret_cast<const T *>(that_v);
}
return VariantCurry<Index + 1, Ts...>::Equal(type_t, that_v, this_v);
}
};
template <std::size_t Index>
struct VariantCurry<Index>
{
inline static void Destroy(std::size_t id, void * data) {}
inline static void Move(std::size_t that_t, void * that_v, void * this_v) {}
inline static void Copy(std::size_t that_t, const void * that_v, void * this_v) {}
inline static bool Equal(std::size_t type_t, const void * that_v, const void * this_v)
{
VerifyOrDie(false);
return false;
}
};
template <typename T, typename TupleType>
class TupleIndexOfType
{
private:
template <std::size_t Index>
static constexpr
typename std::enable_if<std::is_same<T, typename std::tuple_element<Index, TupleType>::type>::value, std::size_t>::type
calculate()
{
return Index;
}
template <std::size_t Index>
static constexpr
typename std::enable_if<!std::is_same<T, typename std::tuple_element<Index, TupleType>::type>::value, std::size_t>::type
calculate()
{
return calculate<Index + 1>();
}
public:
static constexpr std::size_t value = calculate<0>();
};
} // namespace VariantInternal
/**
* @brief
* Represents a type-safe union. An instance of Variant at any given time either holds a value of one of its
* alternative types, or no value.
*
* Example:
* struct Type1 {};
*
* struct Type2 {};
*
* Variant<Type1, Type2> v;
* v.Set<Type1>(); // v contains Type1
* Type1 o1 = v.Get<Type1>();
*/
template <typename... Ts>
struct Variant
{
private:
static constexpr std::size_t kDataSize = std::max({ sizeof(Ts)... });
static constexpr std::size_t kDataAlign = std::max({ alignof(Ts)... });
static constexpr std::size_t kInvalidType = SIZE_MAX;
using Data = typename std::aligned_storage<kDataSize, kDataAlign>::type;
using Curry = VariantInternal::VariantCurry<0, Ts...>;
std::size_t mTypeId;
Data mData;
public:
Variant() : mTypeId(kInvalidType) {}
template <typename T, class... Args>
constexpr explicit Variant(InPlaceTemplateType<T>, Args &&... args) :
mTypeId(VariantInternal::TupleIndexOfType<T, std::tuple<Ts...>>::value)
{
new (&mData) T(std::forward<Args>(args)...);
}
Variant(const Variant<Ts...> & that) : mTypeId(that.mTypeId) { Curry::Copy(that.mTypeId, &that.mData, &mData); }
Variant(Variant<Ts...> && that) : mTypeId(that.mTypeId)
{
Curry::Move(that.mTypeId, &that.mData, &mData);
Curry::Destroy(that.mTypeId, &that.mData);
that.mTypeId = kInvalidType;
}
Variant<Ts...> & operator=(const Variant<Ts...> & that)
{
Curry::Destroy(mTypeId, &mData);
mTypeId = that.mTypeId;
Curry::Copy(that.mTypeId, &that.mData, &mData);
return *this;
}
Variant<Ts...> & operator=(Variant<Ts...> && that)
{
Curry::Destroy(mTypeId, &mData);
mTypeId = that.mTypeId;
Curry::Move(that.mTypeId, &that.mData, &mData);
Curry::Destroy(that.mTypeId, &that.mData);
that.mTypeId = kInvalidType;
return *this;
}
bool operator==(const Variant & other) const
{
return GetType() == other.GetType() && (!Valid() || Curry::Equal(mTypeId, &other.mData, &mData));
}
template <typename T>
bool Is() const
{
return (mTypeId == VariantInternal::TupleIndexOfType<T, std::tuple<Ts...>>::value);
}
std::size_t GetType() const { return mTypeId; }
bool Valid() const { return (mTypeId != kInvalidType); }
template <typename T, typename... Args>
static Variant<Ts...> Create(Args &&... args)
{
return Variant<Ts...>(InPlaceTemplate<T>, std::forward<Args>(args)...);
}
template <typename T, typename... Args>
void Set(Args &&... args)
{
Curry::Destroy(mTypeId, &mData);
new (&mData) T(std::forward<Args>(args)...);
mTypeId = VariantInternal::TupleIndexOfType<T, std::tuple<Ts...>>::value;
}
template <typename T>
T & Get()
{
VerifyOrDie((mTypeId == VariantInternal::TupleIndexOfType<T, std::tuple<Ts...>>::value));
return *reinterpret_cast<T *>(&mData);
}
template <typename T>
const T & Get() const
{
VerifyOrDie((mTypeId == VariantInternal::TupleIndexOfType<T, std::tuple<Ts...>>::value));
return *reinterpret_cast<const T *>(&mData);
}
// Ideally we would suppress this from within Optional.h, where this false positive is coming from. That said suppressing
// here is okay since mTypeId would seemingly only be uninitialized when Variant is in a union.
//
// NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage): Only in a false positive is mTypeId uninitialized.
~Variant() { Curry::Destroy(mTypeId, &mData); }
};
} // namespace chip