Skip to content
Snippets Groups Projects
Commit 54c59352 authored by Olivier BICHLER's avatar Olivier BICHLER
Browse files

Clarified addAttr behavior and added setAttr, changed any implementation

parent a9a6b48d
No related branches found
No related tags found
1 merge request!16Unified interface for attributes
Pipeline #32315 passed
/********************************************************************************
* 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_ */
......@@ -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;
};
}
......
......@@ -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>>);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment