/* * * 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 #include #include #include #include #include #include #include namespace chip { namespace VariantInternal { template struct VariantCurry; template struct VariantCurry { inline static void Destroy(std::size_t id, void * data) { if (id == Index) reinterpret_cast(data)->~T(); else VariantCurry::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(that_v))); else VariantCurry::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(that_v)); } else { VariantCurry::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(this_v) == *reinterpret_cast(that_v); } return VariantCurry::Equal(type_t, that_v, this_v); } }; template struct VariantCurry { 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 class TupleIndexOfType { private: template static constexpr typename std::enable_if::type>::value, std::size_t>::type calculate() { return Index; } template static constexpr typename std::enable_if::type>::value, std::size_t>::type calculate() { return calculate(); } 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 v; * v.Set(); // v contains Type1 * Type1 o1 = v.Get(); */ template 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::type; using Curry = VariantInternal::VariantCurry<0, Ts...>; std::size_t mTypeId; Data mData; public: Variant() : mTypeId(kInvalidType) {} template constexpr explicit Variant(InPlaceTemplateType, Args &&... args) : mTypeId(VariantInternal::TupleIndexOfType>::value) { new (&mData) T(std::forward(args)...); } Variant(const Variant & that) : mTypeId(that.mTypeId) { Curry::Copy(that.mTypeId, &that.mData, &mData); } Variant(Variant && that) : mTypeId(that.mTypeId) { Curry::Move(that.mTypeId, &that.mData, &mData); Curry::Destroy(that.mTypeId, &that.mData); that.mTypeId = kInvalidType; } Variant & operator=(const Variant & that) { Curry::Destroy(mTypeId, &mData); mTypeId = that.mTypeId; Curry::Copy(that.mTypeId, &that.mData, &mData); return *this; } Variant & operator=(Variant && 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 bool Is() const { return (mTypeId == VariantInternal::TupleIndexOfType>::value); } std::size_t GetType() const { return mTypeId; } bool Valid() const { return (mTypeId != kInvalidType); } template static Variant Create(Args &&... args) { return Variant(InPlaceTemplate, std::forward(args)...); } template void Set(Args &&... args) { Curry::Destroy(mTypeId, &mData); new (&mData) T(std::forward(args)...); mTypeId = VariantInternal::TupleIndexOfType>::value; } template T & Get() { VerifyOrDie((mTypeId == VariantInternal::TupleIndexOfType>::value)); return *reinterpret_cast(&mData); } template const T & Get() const { VerifyOrDie((mTypeId == VariantInternal::TupleIndexOfType>::value)); return *reinterpret_cast(&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