From 54c593527ca43342774cdb3226f1cf09fc4e5451 Mon Sep 17 00:00:00 2001 From: Olivier BICHLER <olivier.bichler@cea.fr> Date: Mon, 2 Oct 2023 16:26:24 +0200 Subject: [PATCH] Clarified addAttr behavior and added setAttr, changed any implementation --- include/aidge/utils/Any.hpp | 624 ++++++++++++++++++---- include/aidge/utils/DynamicAttributes.hpp | 52 +- python_binding/utils/pybind_Parameter.cpp | 12 +- 3 files changed, 539 insertions(+), 149 deletions(-) diff --git a/include/aidge/utils/Any.hpp b/include/aidge/utils/Any.hpp index 0310c38cc..0e6571059 100644 --- a/include/aidge/utils/Any.hpp +++ b/include/aidge/utils/Any.hpp @@ -1,154 +1,552 @@ - -/******************************************************************************** - * Copyright (c) 2023 CEA-List +/** + * Origin: https://github.com/claudiofantacci/any + * + * Implementation of N4562 std::experimental::any (merged into C++17 as std::any) + * for C++11 compilers. * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. + * See also: + * + http://en.cppreference.com/w/cpp/any + * + http://en.cppreference.com/w/cpp/experimental/any + * + http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#any + * + https://cplusplus.github.io/LWG/lwg-active.html#2509 * - * SPDX-License-Identifier: EPL-2.0 + * Copyright (c) 2016 Denilson das Mercês Amorim + * Copyright (c) 2018 Claudio Fantacci * - ********************************************************************************/ - -#ifndef AIDGE_ANY_H_ -#define AIDGE_ANY_H_ + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE.md or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef AIDGE_CORE_UTILS_ANY_H_ +#define AIDGE_CORE_UTILS_ANY_H_ -#include <typeinfo> // typeid -#include <type_traits> // std::enable_if_t, std::decay_t, std::is_same, std::is_copy_constructible, std::remove_cv, std::remove_reference -#include <assert.h> -#include <new> +#include <stdexcept> +#include <typeinfo> +#include <type_traits> +#include <utility> -class _any { -private: - /// @brief Operation to perform on the object. - enum _Op { _Op_access, _Op_get_type_info, _Op_clone, _Op_destroy }; - union _Arg { - const std::type_info* _M_typeinfo; - _any* _M_any; - }; +namespace libany +{ - /// @brief Stored data without type information. - void* _M_data; +class bad_any_cast : public std::bad_cast +{ +public: + const char* what() const noexcept override + { + return "bad any_cast"; + } +}; - /// @brief Member function to perform type-related computations on stored data. - void (*_M_manager)(_Op, const _any*, _Arg*); +class any final +{ public: - /// @brief Class to centralize functions and type information in a memory efficient way. - /// @tparam Tp Decayed stored type. - template <typename Tp> - struct Manager { - static void manage(_Op which, const _any* __any, _Arg* __arg) { - auto ptr = static_cast<const Tp*>(__any->_M_data); - switch (which) - { - case _Op_get_type_info: - __arg->_M_typeinfo = &typeid(Tp); - break; - case _Op_clone: - __arg->_M_any->_M_data = new Tp(*ptr); - __arg->_M_any->_M_manager = __any->_M_manager; - break; - case _Op_destroy: - delete ptr; - break; - } + /** + * Constructs an object of type any with an empty state. + */ + any() : + vtable(nullptr) + { } + + /** + * Constructs an object of type any with an equivalent state as other. + */ + any(const any& rhs) : + vtable(rhs.vtable) + { + if(rhs.has_value()) + { + rhs.vtable->copy(rhs.storage, this->storage); } - static Tp* access(const _any* __any) { - return static_cast<Tp*>(__any->_M_data); + } + + + /** + * Constructs an object of type any with a state equivalent to the original state of other. + * rhs is left in a valid but otherwise unspecified state. + */ + any(any&& rhs) noexcept : + vtable(rhs.vtable) + { + if(rhs.has_value()) + { + rhs.vtable->move(rhs.storage, this->storage); + rhs.vtable = nullptr; } + } - // template <typename Up> - // static void create(void* data, Up&& value) { - // data = new Tp(std::forward<Up>(value)); - // } - }; -private: - template<typename _Tp, typename _VTp = std::decay_t<_Tp>> - using _Decay_if_not_any = std::enable_if_t<!std::is_same<_VTp, _any>::value, _VTp>; + /** + * Same effect as this->clear(). + */ + ~any() + { + this->reset(); + } -public: - /// @brief Default constructor - _any() noexcept : _M_manager(nullptr) { } - /// @brief Copy constructor - /// @param __other - _any(const _any& __other) + /** + * Constructs an object of type any that contains an object of type T direct-initialized with std::forward<ValueType>(value). + * T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. + * This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. + */ + template<typename ValueType, typename = typename std::enable_if<!std::is_same<typename std::decay<ValueType>::type, any>::value>::type> + any(ValueType&& value) { - if (!__other._M_manager) - _M_manager = nullptr; - else + static_assert(std::is_copy_constructible<typename std::decay<ValueType>::type>::value, + "T shall satisfy the CopyConstructible requirements."); + this->construct(std::forward<ValueType>(value)); + } + + + /** + * Has the same effect as any(rhs).swap(*this). No effects if an exception is thrown. + */ + any& operator=(const any& rhs) + { + any(rhs).swap(*this); + return *this; + } + + + /** + * Has the same effect as any(std::move(rhs)).swap(*this). + * The state of *this is equivalent to the original state of rhs and rhs is left in a valid + * but otherwise unspecified state. + */ + any& operator=(any&& rhs) noexcept + { + any(std::move(rhs)).swap(*this); + return *this; + } + + + /** + * Has the same effect as any(std::forward<ValueType>(value)).swap(*this). No effect if a exception is thrown. + * T shall satisfy the CopyConstructible requirements, otherwise the program is ill-formed. + * This is because an `any` may be copy constructed into another `any` at any time, so a copy should always be allowed. + */ + template<typename ValueType, typename = typename std::enable_if<!std::is_same<typename std::decay<ValueType>::type, any>::value>::type> + any& operator=(ValueType&& value) + { + static_assert(std::is_copy_constructible<typename std::decay<ValueType>::type>::value, "T shall satisfy the CopyConstructible requirements."); + any(std::forward<ValueType>(value)).swap(*this); + return *this; + } + + + /** + * If not empty, destroys the contained object. + */ + void reset() noexcept + { + if(has_value()) { - _Arg __arg; - __arg._M_any = this; - __other._M_manager(_Op_clone, &__other, &__arg); + this->vtable->destroy(storage); + this->vtable = nullptr; } } - /// @brief Move constructor - /// @param __other - _any(_any&& __other) + + /** + * Returns true if *this has no contained object, otherwise false. + */ + bool has_value() const noexcept + { + return this->vtable != nullptr; + } + + + /** + * If *this has a contained object of type T, typeid(T); otherwise typeid(void). + */ + const std::type_info& type() const noexcept { - if (!__other._M_manager) - _M_manager = nullptr; + return has_value()? this->vtable->type() : typeid(void); + } + + + /** + * Exchange the states of *this and rhs. + */ + void swap(any& other) noexcept + { + if(this->vtable != other.vtable) + { + any tmp(std::move(other)); + + other.vtable = this->vtable; + if(this->vtable != nullptr) + this->vtable->move(this->storage, other.storage); + + this->vtable = tmp.vtable; + if(tmp.vtable != nullptr) + { + tmp.vtable->move(tmp.storage, this->storage); + tmp.vtable = nullptr; + } + } else { - _M_data = __other._M_data; - _M_manager = __other._M_manager; - const_cast<_any*>(&__other)->_M_manager = nullptr; + if(this->vtable != nullptr) + this->vtable->swap(this->storage, other.storage); } } - /// @brief By-value constructor. - /// @tparam T Data type. - /// @tparam VT Decayed data type. - /// @param value - template<typename T, typename VT = _Decay_if_not_any<T>, std::enable_if_t<std::is_copy_constructible<VT>::value, bool> = true> - explicit _any(T&& value) - : _M_manager(&Manager<VT>::manage), - _M_data(new VT{std::forward<T>(value)}) - {} - ~_any() +private: + union storage_union + { + using stack_storage_t = typename std::aligned_storage<2 * sizeof(void*), std::alignment_of<void*>::value>::type; + + void* dynamic; + + stack_storage_t stack; + }; + + + /** + * Base VTable specification. + * + * Note: The caller is responsible for doing .vtable = nullptr after destructful operations + * such as destroy() and/or move(). + */ + struct vtable_type + { + /** + * The type of the object this vtable is for. + */ + const std::type_info& (*type)() noexcept; + + + /** + * Destroys the object in the union. + * The state of the union after this call is unspecified, caller must ensure not to use src anymore. + */ + void(*destroy)(storage_union&) noexcept; + + + /** + * Copies the **inner** content of the src union into the yet unitialized dest union. + * As such, both inner objects will have the same state, but on separate memory locations. + */ + void(*copy)(const storage_union& src, storage_union& dest); + + + /** + * Moves the storage from src to the yet unitialized dest union. + * The state of src after this call is unspecified, caller must ensure not to use src anymore. + */ + void(*move)(storage_union& src, storage_union& dest) noexcept; + + + /** + * Exchanges the storage between lhs and rhs. + */ + void(*swap)(storage_union& lhs, storage_union& rhs) noexcept; + }; + + + /** + * VTable for dynamically allocated storage. + */ + template<typename T> + struct vtable_dynamic + { + static const std::type_info& type() noexcept + { + return typeid(T); + } + + + static void destroy(storage_union& storage) noexcept + { + delete reinterpret_cast<T*>(storage.dynamic); + } + + + static void copy(const storage_union& src, storage_union& dest) + { + dest.dynamic = new T(*reinterpret_cast<const T*>(src.dynamic)); + } + + + static void move(storage_union& src, storage_union& dest) noexcept + { + dest.dynamic = src.dynamic; + src.dynamic = nullptr; + } + + + static void swap(storage_union& lhs, storage_union& rhs) noexcept + { + std::swap(lhs.dynamic, rhs.dynamic); + } + }; + + + /** + * VTable for stack allocated storage. + */ + template<typename T> + struct vtable_stack { - if(_M_manager) { - _M_manager(_Op_destroy, this, nullptr); - _M_manager = nullptr; + static const std::type_info& type() noexcept + { + return typeid(T); } + + + static void destroy(storage_union& storage) noexcept + { + reinterpret_cast<T*>(&storage.stack)->~T(); + } + + + static void copy(const storage_union& src, storage_union& dest) + { + new (&dest.stack) T(reinterpret_cast<const T&>(src.stack)); + } + + + static void move(storage_union& src, storage_union& dest) noexcept + { + /** + * One of the conditions for using vtable_stack is a nothrow move constructor, + * so this move constructor will never throw a exception. + */ + new (&dest.stack) T(std::move(reinterpret_cast<T&>(src.stack))); + destroy(src); + } + + + static void swap(storage_union& lhs, storage_union& rhs) noexcept + { + storage_union tmp_storage; + move(rhs, tmp_storage); + move(lhs, rhs); + move(tmp_storage, lhs); + } + }; + + + /** + * Whether the type T must be dynamically allocated or can be stored on the stack. + */ + template<typename T> + struct requires_allocation : + std::integral_constant<bool, !(std::is_nothrow_move_constructible<T>::value // N4562 6.3/3 [any.class] + && sizeof(T) <= sizeof(storage_union::stack) + && std::alignment_of<T>::value <= std::alignment_of<storage_union::stack_storage_t>::value)> + { }; + + + /** + * Returns the pointer to the vtable of the type T. + */ + template<typename T> + static vtable_type* vtable_for_type() + { + using VTableType = typename std::conditional<requires_allocation<T>::value, vtable_dynamic<T>, vtable_stack<T>>::type; + static vtable_type table = { VTableType::type, VTableType::destroy, VTableType::copy, VTableType::move, VTableType::swap }; + return &table; + } + + +protected: + template<typename T> + friend const T* any_cast(const any* operand) noexcept; + + + template<typename T> + friend T* any_cast(any* operand) noexcept; + + + /** + * Same effect as is_same(this->type(), t); + */ + bool is_typed(const std::type_info& t) const + { + return is_same(this->type(), t); + } + + + /** + * Checks if two type infos are the same. + * If ANY_IMPL_FAST_TYPE_INFO_COMPARE is defined, checks only the address of the + * type infos, otherwise does an actual comparision. Checking addresses is + * only a valid approach when there's no interaction with outside sources + * (other shared libraries and such). + */ + static bool is_same(const std::type_info& a, const std::type_info& b) + { +#ifdef ANY_IMPL_FAST_TYPE_INFO_COMPARE + return &a == &b; +#else + return a == b; +#endif + } + + + /** + * Casts (with no type_info checks) the storage pointer as const T*. + */ + template<typename T> + const T* cast() const noexcept + { + return requires_allocation<typename std::decay<T>::type>::value ? reinterpret_cast<const T*>(storage.dynamic) : reinterpret_cast<const T*>(&storage.stack); } - /// @brief Access type id of the value currently stored - /// @return - const std::type_info& type() const + + /** + * Casts (with no type_info checks) the storage pointer as T*. + */ + template<typename T> + T* cast() noexcept { - if (!_M_manager) - return typeid(void); - _Arg __arg; - _M_manager(_Op_get_type_info, this, &__arg); - return *__arg._M_typeinfo; + return requires_allocation<typename std::decay<T>::type>::value ? reinterpret_cast<T*>(storage.dynamic) : reinterpret_cast<T*>(&storage.stack); + } + + +private: + storage_union storage; // On offset(0) so no padding for align + + vtable_type* vtable; + + + template<typename ValueType, typename T> + typename std::enable_if<requires_allocation<T>::value>::type do_construct(ValueType&& value) + { + storage.dynamic = new T(std::forward<ValueType>(value)); + } + + + template<typename ValueType, typename T> + typename std::enable_if<!requires_allocation<T>::value>::type do_construct(ValueType&& value) + { + new (&storage.stack) T(std::forward<ValueType>(value)); + } + + + /** + * Chooses between stack and dynamic allocation for the type decay_t<ValueType>, + * assigns the correct vtable, and constructs the object on our storage. + */ + template<typename ValueType> + void construct(ValueType&& value) + { + using T = typename std::decay<ValueType>::type; + + this->vtable = vtable_for_type<T>(); + + do_construct<ValueType,T>(std::forward<ValueType>(value)); } }; -/// @brief Access value stored in the object converted in the template type if possible. -/// @tparam _ValueType -/// @param __any -/// @return Stored value. -template<typename _ValueType> -inline _ValueType any_cast(const _any& __any) + +namespace detail +{ + template<typename ValueType> + inline ValueType any_cast_move_if_true(typename std::remove_reference<ValueType>::type* p, std::true_type) + { + return std::move(*p); + } + + + template<typename ValueType> + inline ValueType any_cast_move_if_true(typename std::remove_reference<ValueType>::type* p, std::false_type) + { + return *p; + } +} + + +/** + * Performs *any_cast<add_const_t<remove_reference_t<ValueType>>>(&operand), or throws bad_any_cast on failure. + */ +template<typename ValueType> +inline ValueType any_cast(const any& operand) +{ + auto p = any_cast<typename std::add_const<typename std::remove_reference<ValueType>::type>::type>(&operand); + if(p == nullptr) throw bad_any_cast(); + return *p; +} + + +/** + * Performs *any_cast<remove_reference_t<ValueType>>(&operand), or throws bad_any_cast on failure. + */ +template<typename ValueType> +inline ValueType any_cast(any& operand) +{ + auto p = any_cast<typename std::remove_reference<ValueType>::type>(&operand); + if(p == nullptr) throw bad_any_cast(); + return *p; +} + + +/** + * If ANY_IMPL_ANYCAST_MOVEABLE is not defined, does as N4562 specifies: + * Performs *any_cast<remove_reference_t<ValueType>>(&operand), or throws bad_any_cast on failure. + * + * If ANY_IMPL_ANYCAST_MOVEABLE is defined, does as LWG Defect 2509 specifies [1]: + * If ValueType is MoveConstructible and isn't a lvalue reference, performs + * std::move(*any_cast<remove_reference_t<ValueType>>(&operand)), otherwise + * *any_cast<remove_reference_t<ValueType>>(&operand). + * Throws bad_any_cast on failure. + * + * [1] https://cplusplus.github.io/LWG/lwg-active.html#2509 + */ +template<typename ValueType> +inline ValueType any_cast(any&& operand) +{ +#ifdef ANY_IMPL_ANY_CAST_MOVEABLE + using can_move = std::integral_constant<bool, std::is_move_constructible<ValueType>::value && !std::is_lvalue_reference<ValueType>::value>; +#else + using can_move = std::false_type; +#endif + + auto p = any_cast<typename std::remove_reference<ValueType>::type>(&operand); + if(p == nullptr) throw bad_any_cast(); + return detail::any_cast_move_if_true<ValueType>(p, can_move()); +} + + +/** + * If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object + * contained by operand, otherwise nullptr. + */ +template<typename T> +inline const T* any_cast(const any* operand) noexcept +{ + if(operand == nullptr || !operand->is_typed(typeid(T))) + return nullptr; + else + return operand->cast<T>(); +} + + +/** + * If operand != nullptr && operand->type() == typeid(ValueType), a pointer to the object + * contained by operand, otherwise nullptr. + */ +template<typename T> +inline T* any_cast(any* operand) noexcept { - using _Up = std::remove_cv_t<std::remove_reference_t<_ValueType>>; - assert((std::__or_<std::is_reference<_ValueType>, std::is_copy_constructible<_ValueType>>::value && "Template argument must be a reference or CopyConstructible type")); - assert((std::is_constructible<_ValueType, const _Up&>::value && "Template argument must be constructible from a const value.")); - assert(std::is_object<_Up>::value); - assert(__any.type() == typeid(_Up)); - auto __p = static_cast<_Up*>(__any._M_data); - if (__p) - return static_cast<_ValueType>(*__p); - throw std::bad_cast(); + if(operand == nullptr || !operand->is_typed(typeid(T))) + return nullptr; + else + return operand->cast<T>(); +} + + +inline void swap(any& lhs, any& rhs) noexcept +{ + lhs.swap(rhs); +} + } -#endif /* AIDGE_ANY_H_ */ \ No newline at end of file +#endif /* AIDGE_CORE_UTILS_ANY_H_ */ diff --git a/include/aidge/utils/DynamicAttributes.hpp b/include/aidge/utils/DynamicAttributes.hpp index df38ffea4..1c3ac472e 100644 --- a/include/aidge/utils/DynamicAttributes.hpp +++ b/include/aidge/utils/DynamicAttributes.hpp @@ -28,34 +28,6 @@ namespace Aidge { ///\todo store also a fix-sized code that indicates the type ///\todo managing complex types or excluding non-trivial, non-aggregate types class DynamicAttributes : public Attributes { -private: - template<typename _ValueType> - inline _ValueType& any_cast_ref(const _any& __any) - { - using _Up = std::remove_cv_t<std::remove_reference_t<_ValueType>>; - assert(((std::is_reference<_ValueType>::value || std::is_copy_constructible<_ValueType>::value) && "Template argument must be a reference or CopyConstructible type")); - assert((std::is_constructible<_ValueType, const _Up&>::value && "Template argument must be constructible from a const value.")); - assert(std::is_object<_Up>::value); - assert(__any.type() == typeid(_Up)); - if (_any::Manager<_Up>::access(&__any)) { // assess if _any object is empty - return *static_cast<_ValueType*>(_any::Manager<_Up>::access(&__any)); - } - throw std::bad_cast(); - } - - template<typename _ValueType> - inline const _ValueType& any_cast_ref(const _any& __any) const - { - using _Up = std::remove_cv_t<std::remove_reference_t<_ValueType>>; - assert(((std::is_reference<_ValueType>::value || std::is_copy_constructible<_ValueType>::value) && "Template argument must be a reference or CopyConstructible type")); - assert((std::is_constructible<_ValueType, const _Up&>::value && "Template argument must be constructible from a const value.")); - assert(std::is_object<_Up>::value); - assert(__any.type() == typeid(_Up)); - if (_any::Manager<_Up>::access(&__any)) { // assess if _any object is empty - return *static_cast<const _ValueType*>(_any::Manager<_Up>::access(&__any)); - } - throw std::bad_cast(); - } public: /** * \brief Returning an Attribute identified by its name @@ -67,23 +39,33 @@ public: */ template<class T> T& getAttr(const std::string& name) { - return any_cast_ref<T>(mAttrs.at(name)); + return libany::any_cast<T&>(mAttrs.at(name)); } template<class T> const T& getAttr(const std::string& name) const { - return any_cast_ref<T>(mAttrs.at(name)); + return libany::any_cast<const T&>(mAttrs.at(name)); } - ///\brief Add a Attribute value, identified by its name + ///\brief Add a new Attribute, identified by its name. If it already exists, asserts. ///\tparam T expected Attribute type ///\param name Attribute name ///\param value Attribute value - ///\todo Pass value by ref if large or not trivial - ///\bug If Attribute already exists, its value is changed template<class T> void addAttr(const std::string& name, T&& value) { - mAttrs.emplace(std::make_pair(name, _any(std::forward<T>(value)))); + const auto& res = mAttrs.emplace(std::make_pair(name, libany::any(std::forward<T>(value)))); + assert(res.second && "attribute already exists"); + } + + ///\brief Set an Attribute value, identified by its name. If it already exists, its value (and type, if different) is changed. + ///\tparam T expected Attribute type + ///\param name Attribute name + ///\param value Attribute value + template<class T> void setAttr(const std::string& name, T&& value) + { + auto res = mAttrs.emplace(std::make_pair(name, libany::any(std::forward<T>(value)))); + if (!res.second) + res.first->second = std::move(libany::any(std::forward<T>(value))); } ////////////////////////////////////// @@ -136,7 +118,7 @@ public: #endif private: - std::map<std::string, _any> mAttrs; + std::map<std::string, libany::any> mAttrs; }; } diff --git a/python_binding/utils/pybind_Parameter.cpp b/python_binding/utils/pybind_Parameter.cpp index fcd32c14d..6f6d9980a 100644 --- a/python_binding/utils/pybind_Parameter.cpp +++ b/python_binding/utils/pybind_Parameter.cpp @@ -12,6 +12,7 @@ void init_Attributes(py::module& m){ .def("get_attr", &Attributes::getAttrPy, py::arg("name")); py::class_<DynamicAttributes, std::shared_ptr<DynamicAttributes>, Attributes>(m, "DynamicAttributes") + // add .def("add_attr", &DynamicAttributes::addAttr<bool>) .def("add_attr", &DynamicAttributes::addAttr<int>) .def("add_attr", &DynamicAttributes::addAttr<float>) @@ -19,7 +20,16 @@ void init_Attributes(py::module& m){ .def("add_attr", &DynamicAttributes::addAttr<std::vector<bool>>) .def("add_attr", &DynamicAttributes::addAttr<std::vector<int>>) .def("add_attr", &DynamicAttributes::addAttr<std::vector<float>>) - .def("add_attr", &DynamicAttributes::addAttr<std::vector<std::string>>); + .def("add_attr", &DynamicAttributes::addAttr<std::vector<std::string>>) + // set + .def("set_attr", &DynamicAttributes::setAttr<bool>) + .def("set_attr", &DynamicAttributes::setAttr<int>) + .def("set_attr", &DynamicAttributes::setAttr<float>) + .def("set_attr", &DynamicAttributes::setAttr<std::string>) + .def("set_attr", &DynamicAttributes::setAttr<std::vector<bool>>) + .def("set_attr", &DynamicAttributes::setAttr<std::vector<int>>) + .def("set_attr", &DynamicAttributes::setAttr<std::vector<float>>) + .def("set_attr", &DynamicAttributes::setAttr<std::vector<std::string>>); } } -- GitLab