/** * Copyright (C) 2022-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #pragma once #include #include #include namespace mongo::optimizer { namespace algebra { namespace detail { template inline constexpr bool is_one_of_v = std::disjunction_v...>; template inline constexpr bool is_one_of_f() { return is_one_of_v; } template struct is_unique_t : std::true_type {}; template struct is_unique_t : std::bool_constant() && is_unique_t::value> {}; template inline constexpr bool is_unique_v = is_unique_t::value; // Given the type T find its index in Ts template static inline constexpr int find_index() { static_assert(detail::is_unique_v, "Types must be unique"); constexpr bool matchVector[] = {std::is_same::value...}; for (int index = 0; index < static_cast(sizeof...(Ts)); ++index) { if (matchVector[index]) { return index; } } return -1; } template struct get_type_by_index_impl { using type = typename get_type_by_index_impl::type; }; template struct get_type_by_index_impl<0, T, Ts...> { using type = T; }; // Given the index I return the type from Ts template using get_type_by_index = typename get_type_by_index_impl::type; } // namespace detail /*=====----- * * The overload trick to construct visitors from lambdas. * */ template struct overload : Ts... { using Ts::operator()...; }; template overload(Ts...)->overload; /*=====----- * * Forward declarations * */ template class PolyValue; template class ControlBlockVTable; /*=====----- * * The base control block that PolyValue holds. * * It does not contain anything else by the runtime tag. * */ template class ControlBlock { const int _tag; protected: ControlBlock(int tag) noexcept : _tag(tag) {} public: auto getRuntimeTag() const noexcept { return _tag; } }; /*=====----- * * The concrete control block VTable generator. * * It must be empty ad PolyValue derives from the generators * and we want EBO to kick in. * */ template class ControlBlockVTable { protected: static constexpr int _staticTag = detail::find_index(); static_assert(_staticTag != -1, "Type must be on the list"); using AbstractType = ControlBlock; /*=====----- * * The concrete control block for every type T of Ts. * * It derives from the ControlBlock. All methods are private and only * the friend class ControlBlockVTable can call them. * */ class ConcreteType : public AbstractType { T _t; public: template ConcreteType(Args&&... args) : AbstractType(_staticTag), _t(std::forward(args)...) {} const T* getPtr() const noexcept { return &_t; } T* getPtr() noexcept { return &_t; } }; static constexpr auto concrete(AbstractType* block) noexcept { return static_cast(block); } static constexpr auto concrete(const AbstractType* block) noexcept { return static_cast(block); } public: template static AbstractType* make(Args&&... args) { return new ConcreteType(std::forward(args)...); } static AbstractType* clone(const AbstractType* block) { return new ConcreteType(*concrete(block)); } static void destroy(AbstractType* block) noexcept { delete concrete(block); } static bool compareEq(AbstractType* blockLhs, AbstractType* blockRhs) noexcept { if (blockLhs->getRuntimeTag() == blockRhs->getRuntimeTag()) { return *castConst(blockLhs) == *castConst(blockRhs); } return false; } template static constexpr bool is_v = std::is_base_of_v; template static U* cast(AbstractType* block) noexcept { if constexpr (is_v) { return static_cast(concrete(block)->getPtr()); } else { // gcc bug 81676 (void)block; return nullptr; } } template static const U* castConst(const AbstractType* block) noexcept { if constexpr (is_v) { return static_cast(concrete(block)->getPtr()); } else { // gcc bug 81676 (void)block; return nullptr; } } template static auto visit(V&& v, N& holder, AbstractType* block, Args&&... args) { return v(holder, *cast(block), std::forward(args)...); } template static auto visitConst(V&& v, const N& holder, const AbstractType* block, Args&&... args) { return v(holder, *castConst(block), std::forward(args)...); } }; /*=====----- * * This is a variation on variant and polymorphic value theme. * * A tag based dispatch * * Supported operations: * - construction * - destruction * - clone a = b; * - cast a.cast() * - multi-method cast to common base a.cast() * - multi-method visit */ template class PolyValue : private ControlBlockVTable... { public: using key_type = int; private: static_assert(detail::is_unique_v, "Types must be unique"); static_assert(std::conjunction_v>...>, "VTable base classes must be empty"); ControlBlock* _object{nullptr}; PolyValue(ControlBlock* object) noexcept : _object(object) {} auto tag() const noexcept { return _object->getRuntimeTag(); } static void check(const ControlBlock* object) { if (!object) { throw std::logic_error("PolyValue is empty"); } } static void destroy(ControlBlock* object) noexcept { static constexpr std::array destroyTbl = {&ControlBlockVTable::destroy...}; destroyTbl[object->getRuntimeTag()](object); } template static T* cast(ControlBlock* object) { check(object); static constexpr std::array castTbl = {&ControlBlockVTable::template cast...}; return castTbl[object->getRuntimeTag()](object); } template static const T* castConst(ControlBlock* object) { check(object); static constexpr std::array castTbl = { &ControlBlockVTable::template castConst...}; return castTbl[object->getRuntimeTag()](object); } template static bool is(ControlBlock* object) { check(object); static constexpr std::array isTbl = {ControlBlockVTable::template is_v...}; return isTbl[object->getRuntimeTag()]; } class CompareHelper { ControlBlock* _object{nullptr}; auto tag() const noexcept { return _object->getRuntimeTag(); } public: CompareHelper() = default; CompareHelper(ControlBlock* object) : _object(object) {} bool operator==(const CompareHelper& rhs) const noexcept { static constexpr std::array cmp = {ControlBlockVTable::compareEq...}; return cmp[tag()](_object, rhs._object); } }; class Reference { ControlBlock* _object{nullptr}; auto tag() const noexcept { return _object->getRuntimeTag(); } public: Reference() = default; Reference(ControlBlock* object) : _object(object) {} template using get_t = detail::get_type_by_index; key_type tagOf() const { check(_object); return tag(); } template auto visit(V&& v, Args&&... args) { // unfortunately gcc rejects much nicer code, clang and msvc accept // static constexpr std::array visitTbl = { &ControlBlockVTable::template // visit... }; using FunPtrType = decltype( &ControlBlockVTable, Ts...>::template visit); static constexpr FunPtrType visitTbl[] = { &ControlBlockVTable::template visit...}; check(_object); return visitTbl[tag()](std::forward(v), *this, _object, std::forward(args)...); } template auto visit(V&& v, Args&&... args) const { // unfortunately gcc rejects much nicer code, clang and msvc accept // static constexpr std::array visitTbl = { &ControlBlockVTable::template // visitConst... }; using FunPtrType = decltype( &ControlBlockVTable, Ts...>::template visitConst); static constexpr FunPtrType visitTbl[] = { &ControlBlockVTable::template visitConst...}; check(_object); return visitTbl[tag()](std::forward(v), *this, _object, std::forward(args)...); } template T* cast() { return PolyValue::template cast(_object); } template const T* cast() const { return PolyValue::template castConst(_object); } template bool is() const { return PolyValue::template is(_object); } bool empty() const noexcept { return !_object; } void swap(Reference& other) noexcept { std::swap(other._object, _object); } // Compare references, not the objects themselves. bool operator==(const Reference& rhs) const noexcept { return _object == rhs._object; } bool operator==(const PolyValue& rhs) const noexcept { return rhs == (*this); } auto hash() const noexcept { return std::hash{}(_object); } auto follow() const { return CompareHelper(_object); } friend class PolyValue; }; public: using reference_type = Reference; template static constexpr key_type tagOf() { return ControlBlockVTable::_staticTag; } key_type tagOf() const { check(_object); return tag(); } PolyValue() = delete; PolyValue(const PolyValue& other) { static constexpr std::array cloneTbl = {&ControlBlockVTable::clone...}; if (other._object) { _object = cloneTbl[other.tag()](other._object); } } PolyValue(const Reference& other) { static constexpr std::array cloneTbl = {&ControlBlockVTable::clone...}; if (other._object) { _object = cloneTbl[other.tag()](other._object); } } PolyValue(PolyValue&& other) noexcept { swap(other); } ~PolyValue() noexcept { if (_object) { destroy(_object); } } PolyValue& operator=(PolyValue other) noexcept { swap(other); return *this; } template static PolyValue make(Args&&... args) { return PolyValue{ControlBlockVTable::make(std::forward(args)...)}; } template using get_t = detail::get_type_by_index; template auto visit(V&& v, Args&&... args) { // unfortunately gcc rejects much nicer code, clang and msvc accept // static constexpr std::array visitTbl = { &ControlBlockVTable::template // visit... }; using FunPtrType = decltype(&ControlBlockVTable, Ts...>::template visit); static constexpr FunPtrType visitTbl[] = { &ControlBlockVTable::template visit...}; check(_object); return visitTbl[tag()](std::forward(v), *this, _object, std::forward(args)...); } template auto visit(V&& v, Args&&... args) const { // unfortunately gcc rejects much nicer code, clang and msvc accept // static constexpr std::array visitTbl = { &ControlBlockVTable::template // visitConst... }; using FunPtrType = decltype( &ControlBlockVTable, Ts...>::template visitConst); static constexpr FunPtrType visitTbl[] = { &ControlBlockVTable::template visitConst...}; check(_object); return visitTbl[tag()](std::forward(v), *this, _object, std::forward(args)...); } template T* cast() { return cast(_object); } template const T* cast() const { return castConst(_object); } template bool is() const { return is(_object); } bool empty() const noexcept { return !_object; } void swap(PolyValue& other) noexcept { std::swap(other._object, _object); } bool operator==(const PolyValue& rhs) const noexcept { static constexpr std::array cmp = {ControlBlockVTable::compareEq...}; return cmp[tag()](_object, rhs._object); } bool operator==(const Reference& rhs) const noexcept { static constexpr std::array cmp = {ControlBlockVTable::compareEq...}; return cmp[tag()](_object, rhs._object); } auto ref() { check(_object); return Reference(_object); } auto ref() const { check(_object); return Reference(_object); } }; } // namespace algebra } // namespace mongo::optimizer