diff --git a/CMakeLists.txt b/CMakeLists.txt
index b764086c8e974dc53aadd345cdd287918d599afb..40d8837f41bdc0d8dfd7eac1c5960064967f1efb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -66,6 +66,10 @@ endif()
 
 target_compile_features(${module_name} PRIVATE cxx_std_14)
 
+# -fvisibility=hidden required by pybind11
+target_compile_options(${module_name} PUBLIC
+    $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
+    -fvisibility=hidden>)
 target_compile_options(${module_name} PRIVATE
     $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
     -Wall -Wextra -Wold-style-cast -Winline -pedantic -Werror=narrowing -Wshadow $<$<BOOL:${WERROR}>:-Werror>>)
diff --git a/aidge_core/unit_tests/test_operator_binding.py b/aidge_core/unit_tests/test_operator_binding.py
index 8898bc5a7ac6ce771cab8402933d464c1f04316f..fc60f52274162155f8f891bf86c22c9a13b241f4 100644
--- a/aidge_core/unit_tests/test_operator_binding.py
+++ b/aidge_core/unit_tests/test_operator_binding.py
@@ -30,36 +30,67 @@ class test_operator_binding(unittest.TestCase):
         self.assertNotEqual(gop.name(), "")
 
     def test_param_bool(self):
-        self.generic_operator.add_parameter("bool", True)
-        self.assertEqual(self.generic_operator.get_parameter("bool"), True)
+        self.generic_operator.add_attr("bool", True)
+        self.assertEqual(self.generic_operator.has_attr("bool"), True)
+        self.assertEqual(self.generic_operator.get_attr("bool"), True)
+        self.assertEqual(self.generic_operator.get_attr_type("bool"), "bool")
+        self.assertEqual(self.generic_operator.get_attrs_name(), {"bool"})
+        self.generic_operator.del_attr("bool")
+        self.assertEqual(self.generic_operator.has_attr("bool"), False)
+        self.assertEqual(len(self.generic_operator.get_attrs_name()), 0)
 
     def test_param_int(self):
-        self.generic_operator.add_parameter("int", 1)
-        self.assertEqual(self.generic_operator.get_parameter("int"), 1)
+        self.generic_operator.add_attr("int", 1)
+        self.assertEqual(self.generic_operator.get_attr("int"), 1)
 
     def test_param_float(self):
-        self.generic_operator.add_parameter("float", 2.0)
-        self.assertEqual(self.generic_operator.get_parameter("float"), 2.0)
+        self.generic_operator.add_attr("float", 2.0)
+        self.assertEqual(self.generic_operator.get_attr("float"), 2.0)
 
     def test_param_str(self):
-        self.generic_operator.add_parameter("str", "value")
-        self.assertEqual(self.generic_operator.get_parameter("str"), "value")
+        self.generic_operator.add_attr("str", "value")
+        self.assertEqual(self.generic_operator.get_attr("str"), "value")
 
     def test_param_l_int(self):
-        self.generic_operator.add_parameter("l_int", [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
-        self.assertEqual(self.generic_operator.get_parameter("l_int"), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
+        self.generic_operator.add_attr("l_int", [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
+        self.assertEqual(self.generic_operator.get_attr("l_int"), [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
 
     def test_param_l_bool(self):
-        self.generic_operator.add_parameter("l_bool", [True, False, False, True])
-        self.assertEqual(self.generic_operator.get_parameter("l_bool"), [True, False, False, True])
+        self.generic_operator.add_attr("l_bool", [True, False, False, True])
+        self.assertEqual(self.generic_operator.get_attr("l_bool"), [True, False, False, True])
 
     def test_param_l_float(self):
-        self.generic_operator.add_parameter("l_float", [2.0, 1.0])
-        self.assertEqual(self.generic_operator.get_parameter("l_float"), [2.0, 1.0])
+        self.generic_operator.add_attr("l_float", [2.0, 1.0])
+        self.assertEqual(self.generic_operator.get_attr("l_float"), [2.0, 1.0])
 
     def test_param_l_str(self):
-        self.generic_operator.add_parameter("l_str", ["ok"])
-        self.assertEqual(self.generic_operator.get_parameter("l_str"), ["ok"])
+        self.generic_operator.add_attr("l_str", ["ok"])
+        self.assertEqual(self.generic_operator.get_attr("l_str"), ["ok"])
+
+    def test_dynamicattribute_binding(self):
+        # Check original C++ attributes are binded
+        attrs = aidge_core.test_DynamicAttributes_binding()
+        self.assertEqual(attrs.has_attr("a"), True)
+        self.assertEqual(attrs.get_attr("a"), 42)
+        self.assertEqual(attrs.has_attr("b"), True)
+        self.assertEqual(attrs.get_attr("b"), "test")
+        self.assertEqual(attrs.has_attr("c"), True)
+        self.assertEqual(attrs.get_attr("c"), [True, False, True])
+        self.assertEqual(attrs.get_attrs_name(), {"a", "b", "c"})
+        self.assertEqual(attrs.has_attr("d"), False)
+
+        # Add Python attributes
+        attrs.add_attr("d", 18.56)
+        self.assertEqual(attrs.get_attr("d"), 18.56)
+        self.assertEqual(attrs.has_attr("d"), True)
+        self.assertEqual(attrs.get_attrs_name(), {"a", "b", "c", "d"})
+        self.assertEqual(attrs.has_attr("e"), False)
+
+        # Check that added Python attribute is accessible in C++
+        # Return the value of an attribute named "d" of type float64 (double in C++)
+        self.assertEqual(aidge_core.test_DynamicAttributes_binding_check(attrs), 18.56)
+        attrs.set_attr("d", 23.89)
+        self.assertEqual(aidge_core.test_DynamicAttributes_binding_check(attrs), 23.89)
 
     def test_compute_output_dims(self):
         in_dims=[25, 25]
diff --git a/aidge_core/unit_tests/test_parameters.py b/aidge_core/unit_tests/test_parameters.py
index 1e24276745312d4483c268156963e0efe413b46c..566650713c36236c19763f466ee906970466c02e 100644
--- a/aidge_core/unit_tests/test_parameters.py
+++ b/aidge_core/unit_tests/test_parameters.py
@@ -11,7 +11,7 @@ SPDX-License-Identifier: EPL-2.0
 import unittest
 import aidge_core
 
-class test_parameters(unittest.TestCase):
+class test_attributes(unittest.TestCase):
     """Very basic test to make sure the python APi is not broken.
     Can be remove in later stage of the developpement.
     """
@@ -27,21 +27,21 @@ class test_parameters(unittest.TestCase):
         out_channels = 8
         k_dims = [2, 2]
         conv_op = aidge_core.Conv2D(in_channels , out_channels, k_dims).get_operator()
-        self.assertEqual(conv_op.get("InChannels"), in_channels)
-        self.assertEqual(conv_op.get("OutChannels"), out_channels)
-        self.assertEqual(conv_op.get("KernelDims"), k_dims)
+        self.assertEqual(conv_op.get_attr("InChannels"), in_channels)
+        self.assertEqual(conv_op.get_attr("OutChannels"), out_channels)
+        self.assertEqual(conv_op.get_attr("KernelDims"), k_dims)
 
     def test_fc(self):
         out_channels = 8
         nb_bias = True
         fc_op = aidge_core.FC(out_channels, nb_bias).get_operator()
-        self.assertEqual(fc_op.get("OutChannels"), out_channels)
-        self.assertEqual(fc_op.get("NoBias"), nb_bias)
+        self.assertEqual(fc_op.get_attr("OutChannels"), out_channels)
+        self.assertEqual(fc_op.get_attr("NoBias"), nb_bias)
 
     def test_matmul(self):
         out_channels = 8
         matmul_op = aidge_core.MatMul(out_channels).get_operator()
-        self.assertEqual(matmul_op.get("OutChannels"), out_channels)
+        self.assertEqual(matmul_op.get_attr("OutChannels"), out_channels)
 
     def test_producer_1D(self):
         dims = [5]
@@ -71,7 +71,7 @@ class test_parameters(unittest.TestCase):
     def test_leaky_relu(self):
         negative_slope = 0.25
         leakyrelu_op = aidge_core.LeakyReLU(negative_slope).get_operator()
-        self.assertEqual(leakyrelu_op.get("NegativeSlope"), negative_slope)
+        self.assertEqual(leakyrelu_op.get_attr("NegativeSlope"), negative_slope)
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/include/aidge/aidge.hpp b/include/aidge/aidge.hpp
index 9c0d8c0b321892d60f40d52eb2a44d4d0fec3a2c..47ded2a462477958320bfad3ad84e6b8f6ef6082 100644
--- a/include/aidge/aidge.hpp
+++ b/include/aidge/aidge.hpp
@@ -42,8 +42,9 @@
 #include "aidge/operator/Softmax.hpp"
 #include "aidge/operator/Scaling.hpp"
 #include "aidge/scheduler/Scheduler.hpp"
-#include "aidge/utils/CParameter.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/Attributes.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
+#include "aidge/utils/DynamicAttributes.hpp"
 #include "aidge/utils/Recipies.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
diff --git a/include/aidge/data/Data.hpp b/include/aidge/data/Data.hpp
index 81b7810a8a548df7e5a2829b1a31cbe337491382..02f4df320d87d1bb02edfa5c11ffe8bc7f560986 100644
--- a/include/aidge/data/Data.hpp
+++ b/include/aidge/data/Data.hpp
@@ -12,7 +12,7 @@
 #ifndef AIDGE_DATA_H_
 #define AIDGE_DATA_H_
 
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/Attributes.hpp"
 
 namespace Aidge {
 enum class DataType {
diff --git a/include/aidge/hook/hook.hpp b/include/aidge/hook/hook.hpp
index 0448659b937c3498f57cae9935196ef2f38ecf6d..28f7ef5cddbc649af50209ba77527b8b75d731b7 100644
--- a/include/aidge/hook/hook.hpp
+++ b/include/aidge/hook/hook.hpp
@@ -17,7 +17,7 @@
 #ifndef Hook_H_
 #define Hook_H_
 
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/Attributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include <memory>
 
diff --git a/include/aidge/operator/Add.hpp b/include/aidge/operator/Add.hpp
index 303092911ae369473c1f3d6b7f122e3068d77028..1e0f17e6db9278e7edf2a11918472c084561a308 100644
--- a/include/aidge/operator/Add.hpp
+++ b/include/aidge/operator/Add.hpp
@@ -48,7 +48,7 @@ public:
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     Add_Op(const Add_Op<NUM>& op)
diff --git a/include/aidge/operator/AvgPooling.hpp b/include/aidge/operator/AvgPooling.hpp
index 2fbff53c30e376e80d07f0859851057177bf0868..b29463c675eb8516e02b83ad47816e9e9aa5d147 100644
--- a/include/aidge/operator/AvgPooling.hpp
+++ b/include/aidge/operator/AvgPooling.hpp
@@ -21,17 +21,17 @@
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/operator/Producer.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class AvgPoolingParam { StrideDims, KernelDims, PaddingDims };
+enum class AvgPoolingAttr { StrideDims, KernelDims, PaddingDims };
 
 template <DimIdx_t DIM>
 class AvgPooling_Op : public Operator,
                 public Registrable<AvgPooling_Op<DIM>, std::string, std::unique_ptr<OperatorImpl>(const AvgPooling_Op<DIM> &)>,
-                public Parameterizable<AvgPoolingParam,
+                public StaticAttributes<AvgPoolingAttr,
                                        std::array<DimSize_t, DIM>,
                                        std::array<DimSize_t, DIM>,
                                        std::array<DimSize_t, (DIM<<1) >> {
@@ -45,30 +45,30 @@ public:
 
     AvgPooling_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<AvgPoolingParam,
+    using Attributes_ = StaticAttributes<AvgPoolingAttr,
                                              std::array<DimSize_t, DIM>,
                                              std::array<DimSize_t, DIM>,
                                              std::array<DimSize_t, (DIM<<1)> >;
-    template <AvgPoolingParam e>
-    using param = typename Parameterizable_::template param<e>;
+    template <AvgPoolingAttr e>
+    using attr = typename Attributes_::template attr<e>;
 
     constexpr AvgPooling_Op(const std::array<DimSize_t, DIM> &kernel_dims,
                             const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
                             const std::array<DimSize_t, (DIM<<1)> &padding_dims = create_array<DimSize_t,(DIM<<1)>(0))
         : Operator(Type),
-          Parameterizable_(param<AvgPoolingParam::StrideDims>(stride_dims),
-                           param<AvgPoolingParam::KernelDims>(kernel_dims),
-                           param<AvgPoolingParam::PaddingDims>(padding_dims)) {
+          Attributes_(attr<AvgPoolingAttr::StrideDims>(stride_dims),
+                           attr<AvgPoolingAttr::KernelDims>(kernel_dims),
+                           attr<AvgPoolingAttr::PaddingDims>(padding_dims)) {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     AvgPooling_Op(const AvgPooling_Op<DIM>& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -96,13 +96,13 @@ public:
         if (!mInput->empty()) {
             std::array<DimSize_t, DIM + 2> outputDims = {};
 
-            for (std::size_t dim = 0; dim < this->template get<AvgPoolingParam::KernelDims>().size() ; ++dim) {
+            for (std::size_t dim = 0; dim < this->template getAttr<AvgPoolingAttr::KernelDims>().size() ; ++dim) {
                 outputDims[dim+2] = 1 + static_cast<DimSize_t>(
                                             std::floor(static_cast<float>(mInput->dims()[dim+2] -
-                                                                    this->template get<AvgPoolingParam::KernelDims>()[dim] +
-                                                                    this->template get<AvgPoolingParam::PaddingDims>()[dim] +
-                                                                    this->template get<AvgPoolingParam::PaddingDims>()[dim+DIM]) /
-                                            static_cast<float>(this->template get<AvgPoolingParam::StrideDims>()[dim])));
+                                                                    this->template getAttr<AvgPoolingAttr::KernelDims>()[dim] +
+                                                                    this->template getAttr<AvgPoolingAttr::PaddingDims>()[dim] +
+                                                                    this->template getAttr<AvgPoolingAttr::PaddingDims>()[dim+DIM]) /
+                                            static_cast<float>(this->template getAttr<AvgPoolingAttr::StrideDims>()[dim])));
             }
             outputDims[1] = mInput->dims()[1];
             outputDims[0] = mInput->dims()[0];
@@ -189,7 +189,7 @@ inline std::shared_ptr<Node> AvgPooling(
 
 namespace {
 template <>
-const char *const EnumStrings<Aidge::AvgPoolingParam>::data[] = {"StrideDims",
+const char *const EnumStrings<Aidge::AvgPoolingAttr>::data[] = {"StrideDims",
                                                           "KernelDims", "PaddingDims"};
 }
 
diff --git a/include/aidge/operator/BatchNorm.hpp b/include/aidge/operator/BatchNorm.hpp
index c95ecac92d14be6d56edd7abda6c20b011e65aba..90a6be7222ee1b3e377520f2bc612a72c2ba4ab3 100644
--- a/include/aidge/operator/BatchNorm.hpp
+++ b/include/aidge/operator/BatchNorm.hpp
@@ -21,17 +21,17 @@
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/operator/Producer.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 
 namespace Aidge {
-enum class BatchNormParam { Epsilon, Momentum };
+enum class BatchNormAttr { Epsilon, Momentum };
 
 
 template <DimIdx_t DIM>
 class BatchNorm_Op : public Operator,
                 public Registrable<BatchNorm_Op<DIM>, std::string, std::unique_ptr<OperatorImpl>(const BatchNorm_Op<DIM> &)>,
-                public Parameterizable<BatchNormParam, float, float> {
+                public StaticAttributes<BatchNormAttr, float, float> {
 public:
     // FIXME: change accessibility
     std::array<std::shared_ptr<Tensor>, 5> mInputs = {std::make_shared<Tensor>(), std::make_shared<Tensor>(),
@@ -44,24 +44,25 @@ public:
 
     BatchNorm_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<BatchNormParam, float, float>;
-    template <BatchNormParam e>
-    using param = typename Parameterizable_::template param<e>;
+    using Attributes_ = StaticAttributes<BatchNormAttr, float, float>;
+    template <BatchNormAttr e>
+    using attr = typename Attributes_::template attr<e>;
 
     constexpr BatchNorm_Op(float epsilon, float momentum)
         : Operator(Type),
-          Parameterizable_(param<BatchNormParam::Epsilon>(epsilon),
-                           param<BatchNormParam::Momentum>(momentum)) {
+          Attributes_(attr<BatchNormAttr::Epsilon>(epsilon),
+                           attr<BatchNormAttr::Momentum>(momentum)),
+          mOutput(std::make_shared<Tensor>()) {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     BatchNorm_Op(const BatchNorm_Op<DIM>& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -177,7 +178,7 @@ inline std::shared_ptr<Node> BatchNorm(const float epsilon = 1.0e-5F,
 
 namespace {
 template <>
-const char *const EnumStrings<Aidge::BatchNormParam>::data[] = { "Epsilon", "Momentum" };
+const char *const EnumStrings<Aidge::BatchNormAttr>::data[] = { "Epsilon", "Momentum" };
 }
 
 #endif //AIDGE_CORE_OPERATOR_BATCHNORM_H_
diff --git a/include/aidge/operator/Conv.hpp b/include/aidge/operator/Conv.hpp
index 7113adb41d051d67e22456bbac05c00aa15333ab..22553080c6d4d8359149b3b34c5d040e5e900c4d 100644
--- a/include/aidge/operator/Conv.hpp
+++ b/include/aidge/operator/Conv.hpp
@@ -21,17 +21,17 @@
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/operator/Producer.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class ConvParam { StrideDims, DilationDims, InChannels, OutChannels, KernelDims, PaddingDims };
+enum class ConvAttr { StrideDims, DilationDims, InChannels, OutChannels, KernelDims, PaddingDims };
 
 template <DimIdx_t DIM>
 class Conv_Op : public Operator,
                 public Registrable<Conv_Op<DIM>, std::string, std::unique_ptr<OperatorImpl>(const Conv_Op<DIM> &)>,
-                public Parameterizable<ConvParam, std::array<DimSize_t, DIM>, std::array<DimSize_t, DIM>, DimSize_t,
+                public StaticAttributes<ConvAttr, std::array<DimSize_t, DIM>, std::array<DimSize_t, DIM>, DimSize_t,
                                        DimSize_t, std::array<DimSize_t, DIM>, std::array<DimSize_t, (DIM<<1) >> {
 public:
     // FIXME: change accessibility
@@ -44,10 +44,10 @@ public:
 
     Conv_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<ConvParam, std::array<DimSize_t, DIM>, std::array<DimSize_t, DIM>,
+    using Attributes_ = StaticAttributes<ConvAttr, std::array<DimSize_t, DIM>, std::array<DimSize_t, DIM>,
                                              DimSize_t, DimSize_t, std::array<DimSize_t, DIM>, std::array<DimSize_t, (DIM<<1) >>;
-    template <ConvParam e>
-    using param = typename Parameterizable_::template param<e>;
+    template <ConvAttr e>
+    using attr = typename Attributes_::template attr<e>;
 
     constexpr Conv_Op(DimSize_t in_channels,
                       DimSize_t out_channels,
@@ -56,22 +56,22 @@ public:
                       const std::array<DimSize_t, (DIM<<1)> &padding_dims = create_array<DimSize_t,(DIM<<1)>(0),
                       const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
         : Operator(Type),
-          Parameterizable_(param<ConvParam::StrideDims>(stride_dims),
-                           param<ConvParam::DilationDims>(dilation_dims),
-                           param<ConvParam::InChannels>(in_channels),
-                           param<ConvParam::OutChannels>(out_channels),
-                           param<ConvParam::KernelDims>(kernel_dims),
-                           param<ConvParam::PaddingDims>(padding_dims)) {
+          Attributes_(attr<ConvAttr::StrideDims>(stride_dims),
+                           attr<ConvAttr::DilationDims>(dilation_dims),
+                           attr<ConvAttr::InChannels>(in_channels),
+                           attr<ConvAttr::OutChannels>(out_channels),
+                           attr<ConvAttr::KernelDims>(kernel_dims),
+                           attr<ConvAttr::PaddingDims>(padding_dims)) {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     Conv_Op(const Conv_Op<DIM>& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -111,19 +111,19 @@ public:
         if (!mInputs[0]->empty()) {
             std::array<DimSize_t, DIM + 2> outputDims = {};
 
-            for (std::size_t dim = 0; dim < this->template get<ConvParam::KernelDims>().size() ; ++dim) {
-                const DimSize_t kernelExtent = this->template get<ConvParam::DilationDims>()[dim] *
-                                                       (this->template get<ConvParam::KernelDims>()[dim] - 1) +
+            for (std::size_t dim = 0; dim < this->template getAttr<ConvAttr::KernelDims>().size() ; ++dim) {
+                const DimSize_t kernelExtent = this->template getAttr<ConvAttr::DilationDims>()[dim] *
+                                                       (this->template getAttr<ConvAttr::KernelDims>()[dim] - 1) +
                                                1;
 
                 outputDims[dim+2] = 1 + static_cast<DimSize_t>(
                         floor(static_cast<float>(mInputs[0]->dims()[dim+2] - kernelExtent +
-                                                 this->template get<ConvParam::PaddingDims>()[dim] +
-                                                 this->template get<ConvParam::PaddingDims>()[dim+DIM]) /
-                              static_cast<float>(this->template get<ConvParam::StrideDims>()[dim])));
+                                                 this->template getAttr<ConvAttr::PaddingDims>()[dim] +
+                                                 this->template getAttr<ConvAttr::PaddingDims>()[dim+DIM]) /
+                              static_cast<float>(this->template getAttr<ConvAttr::StrideDims>()[dim])));
             }
 
-            outputDims[1] = this->template get<ConvParam::OutChannels>();
+            outputDims[1] = this->template getAttr<ConvAttr::OutChannels>();
             outputDims[0] = mInputs[0]->dims()[0];
             mOutput->resize(outputDims);
         }
@@ -216,7 +216,7 @@ inline std::shared_ptr<Node> Conv(
 
 namespace {
 template <>
-const char *const EnumStrings<Aidge::ConvParam>::data[] = {
+const char *const EnumStrings<Aidge::ConvAttr>::data[] = {
     "StrideDims",
     "DilationDims",
     "InChannels",
diff --git a/include/aidge/operator/ConvDepthWise.hpp b/include/aidge/operator/ConvDepthWise.hpp
index 12d15328cbabbe5b066fa2fb375adecd7935c889..7a4db68bae2f42eb892dd7240463e7363753b5a7 100644
--- a/include/aidge/operator/ConvDepthWise.hpp
+++ b/include/aidge/operator/ConvDepthWise.hpp
@@ -21,17 +21,17 @@
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/operator/Producer.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class ConvDepthWiseParam { StrideDims, DilationDims, Channels, KernelDims, PaddingDims };
+enum class ConvDepthWiseAttr { StrideDims, DilationDims, Channels, KernelDims, PaddingDims };
 
 template <DimIdx_t DIM>
 class ConvDepthWise_Op : public Operator,
                 public Registrable<ConvDepthWise_Op<DIM>, std::string, std::unique_ptr<OperatorImpl>(const ConvDepthWise_Op<DIM> &)>,
-                public Parameterizable<ConvDepthWiseParam,
+                public StaticAttributes<ConvDepthWiseAttr,
                                        std::array<DimSize_t, DIM>,
                                        std::array<DimSize_t, DIM>,
                                        DimSize_t,
@@ -48,35 +48,35 @@ class ConvDepthWise_Op : public Operator,
 
     ConvDepthWise_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<ConvDepthWiseParam,
+    using Attributes_ = StaticAttributes<ConvDepthWiseAttr,
                                              std::array<DimSize_t, DIM>,
                                              std::array<DimSize_t, DIM>,
                                              DimSize_t,
                                              std::array<DimSize_t, DIM>,
                                              std::array<DimSize_t, (DIM<<1) >>;
-    template <ConvDepthWiseParam e>
-    using param = typename Parameterizable_::template param<e>;
+    template <ConvDepthWiseAttr e>
+    using attr = typename Attributes_::template attr<e>;
 
     constexpr ConvDepthWise_Op(const std::array<DimSize_t, DIM> &kernel_dims,
                                const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
                                const std::array<DimSize_t, (DIM<<1)> &padding_dims = create_array<DimSize_t,(DIM<<1)>(0),
                                const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
         : Operator(Type),
-          Parameterizable_(param<ConvDepthWiseParam::StrideDims>(stride_dims),
-                           param<ConvDepthWiseParam::DilationDims>(dilation_dims),
-                           param<ConvDepthWiseParam::Channels>(0),
-                           param<ConvDepthWiseParam::KernelDims>(kernel_dims),
-                           param<ConvDepthWiseParam::PaddingDims>(padding_dims)) {
+          Attributes_(attr<ConvDepthWiseAttr::StrideDims>(stride_dims),
+                           attr<ConvDepthWiseAttr::DilationDims>(dilation_dims),
+                           attr<ConvDepthWiseAttr::Channels>(0),
+                           attr<ConvDepthWiseAttr::KernelDims>(kernel_dims),
+                           attr<ConvDepthWiseAttr::PaddingDims>(padding_dims)) {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     ConvDepthWise_Op(const ConvDepthWise_Op<DIM>& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -103,19 +103,19 @@ class ConvDepthWise_Op : public Operator,
         if (!mInputs[0]->empty()) {
             std::array<DimSize_t, DIM + 2> outputDims = {};
 
-            for (std::size_t dim = 0; dim < this->template get<ConvDepthWiseParam::KernelDims>().size() ; ++dim) {
-                const DimSize_t kernelExtent = this->template get<ConvDepthWiseParam::DilationDims>()[dim] *
-                                                       (this->template get<ConvDepthWiseParam::KernelDims>()[dim] - 1) +
+            for (std::size_t dim = 0; dim < this->template getAttr<ConvDepthWiseAttr::KernelDims>().size() ; ++dim) {
+                const DimSize_t kernelExtent = this->template getAttr<ConvDepthWiseAttr::DilationDims>()[dim] *
+                                                       (this->template getAttr<ConvDepthWiseAttr::KernelDims>()[dim] - 1) +
                                                1;
 
                 outputDims[dim+2] = 1 + static_cast<DimSize_t>(
                         floor(static_cast<float>(mInputs[0]->dims()[dim+2] - kernelExtent +
-                                                 this->template get<ConvDepthWiseParam::PaddingDims>()[dim] +
-                                                 this->template get<ConvDepthWiseParam::PaddingDims>()[dim+DIM]) /
-                              static_cast<float>(this->template get<ConvDepthWiseParam::StrideDims>()[dim])));
+                                                 this->template getAttr<ConvDepthWiseAttr::PaddingDims>()[dim] +
+                                                 this->template getAttr<ConvDepthWiseAttr::PaddingDims>()[dim+DIM]) /
+                              static_cast<float>(this->template getAttr<ConvDepthWiseAttr::StrideDims>()[dim])));
             }
-            this->template get<ConvDepthWiseParam::Channels>() = mInputs[0]->dims()[1];
-            // std::array<DimSize_t, DIM+2> weightDims = append(mInputs[0]->dims()[1],append(1, this->template get<ConvDepthWiseParam::KernelDims>()));
+            this->template getAttr<ConvDepthWiseAttr::Channels>() = mInputs[0]->dims()[1];
+            // std::array<DimSize_t, DIM+2> weightDims = append(mInputs[0]->dims()[1],append(1, this->template getAttr<ConvDepthWiseAttr::KernelDims>()));
             // if (mInputs[1]->empty()) {
             //     mInputs[1]->resize(weightDims);
             // }
@@ -212,7 +212,7 @@ inline std::shared_ptr<Node> ConvDepthWise(
 
 namespace {
 template <>
-const char *const EnumStrings<Aidge::ConvDepthWiseParam>::data[] = {"StrideDims", "DilationDims", "Channels",
+const char *const EnumStrings<Aidge::ConvDepthWiseAttr>::data[] = {"StrideDims", "DilationDims", "Channels",
                                                           "KernelDims", "PaddingDims"};
 }
 
diff --git a/include/aidge/operator/FC.hpp b/include/aidge/operator/FC.hpp
index 73cdab54c2cfade6fbd397d33d537b16cb5245f1..127d39a8bdfdd233cdac9e1ca6cf0bf85f656d16 100644
--- a/include/aidge/operator/FC.hpp
+++ b/include/aidge/operator/FC.hpp
@@ -23,17 +23,17 @@
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/operator/Producer.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 
 namespace Aidge {
-enum class FCParam { OutChannels, NoBias };
+enum class FCAttr { OutChannels, NoBias };
 
 class FC_Op : public Operator,
               public Registrable<FC_Op,
                                  std::string,
                                  std::unique_ptr<OperatorImpl>(const FC_Op &)>,
-              public Parameterizable<FCParam, DimSize_t, bool> {
+              public StaticAttributes<FCAttr, DimSize_t, bool> {
 public:
     // FIXME: change accessibility
     std::array<std::shared_ptr<Tensor>, 3> mInputs = {std::make_shared<Tensor>(), std::make_shared<Tensor>(), std::make_shared<Tensor>()};
@@ -44,25 +44,25 @@ public:
 
     FC_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<FCParam, DimSize_t, bool>;
-    template <FCParam e> using param = typename Parameterizable_::template param<e>;
+    using Attributes_ = StaticAttributes<FCAttr, DimSize_t, bool>;
+    template <FCAttr e> using attr = typename Attributes_::template attr<e>;
 
     FC_Op(DimSize_t out_channels, bool noBias)
             : Operator(Type),
-            Parameterizable_(
-                param<FCParam::OutChannels>(out_channels),
-                param<FCParam::NoBias>(noBias))
+            Attributes_(
+                attr<FCAttr::OutChannels>(out_channels),
+                attr<FCAttr::NoBias>(noBias))
     {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     FC_Op(const FC_Op& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -82,7 +82,7 @@ public:
         assert(inputIdx < 3 && "operators supports only 3 inputs");
         assert(strcmp(data->type(), Tensor::Type)==0 && "input data must be of Tensor type");
         if (inputIdx == 2) {
-            assert(std::dynamic_pointer_cast<Tensor>(data)->size() == ((this->template get<FCParam::NoBias>()) == false ? static_cast<std::size_t>(this->template get<FCParam::OutChannels>()) : 0));
+            assert(std::dynamic_pointer_cast<Tensor>(data)->size() == ((this->template getAttr<FCAttr::NoBias>()) == false ? static_cast<std::size_t>(this->template getAttr<FCAttr::OutChannels>()) : 0));
             assert(std::dynamic_pointer_cast<Tensor>(data)->nbDims() == 1);
         }
         mInputs[inputIdx] = std::dynamic_pointer_cast<Tensor>(data);
@@ -93,9 +93,9 @@ public:
     void computeOutputDims() override final {
         if (!mInputs[0]->empty()) {
             // <in_features**, out_channels>
-            std::array<DimSize_t, 2> weightDims = {this->template get<FCParam::OutChannels>(), static_cast<DimSize_t>(mInputs[0]->sizeM1())};
+            std::array<DimSize_t, 2> weightDims = {this->template getAttr<FCAttr::OutChannels>(), static_cast<DimSize_t>(mInputs[0]->sizeM1())};
             // <out_channels, batch>
-            std::array<DimSize_t, 2> outputDims = {mInputs[0]->dims()[0], this->template get<FCParam::OutChannels>()};
+            std::array<DimSize_t, 2> outputDims = {mInputs[0]->dims()[0], this->template getAttr<FCAttr::OutChannels>()};
 
             mInputs[1]->resize(weightDims);
             mOutput->resize(outputDims);
@@ -171,7 +171,7 @@ inline std::shared_ptr<Node> FC(DimSize_t out_channels, bool noBias = false, con
 
 namespace {
 template <>
-const char *const EnumStrings<Aidge::FCParam>::data[] = {"OutChannels",
+const char *const EnumStrings<Aidge::FCAttr>::data[] = {"OutChannels",
                                                         "NoBias"};
 }
 
diff --git a/include/aidge/operator/GenericOperator.hpp b/include/aidge/operator/GenericOperator.hpp
index 184100174714df5fc059e374cb85549f6bfd4135..1e51866177acf80441f236070aea9dee6145bc19 100644
--- a/include/aidge/operator/GenericOperator.hpp
+++ b/include/aidge/operator/GenericOperator.hpp
@@ -20,18 +20,18 @@
 
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
-#include "aidge/utils/CParameter.hpp"
+#include "aidge/utils/DynamicAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
 class GenericOperator_Op
     : public Operator,
-      public Registrable<GenericOperator_Op, std::string, std::unique_ptr<OperatorImpl>(std::shared_ptr<GenericOperator_Op>)> {
+      public Registrable<GenericOperator_Op, std::string, std::unique_ptr<OperatorImpl>(std::shared_ptr<GenericOperator_Op>)>,
+      public DynamicAttributes {
    private:
     using ComputeDimsFunc = std::function<std::vector<std::vector<size_t>>(const std::vector<std::vector<size_t>>&)>;
 
-    CParameter mParams;
     IOIndex_t mNbDataIn;
     IOIndex_t mNbIn;
     IOIndex_t mNbOut;
@@ -54,11 +54,11 @@ class GenericOperator_Op
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     GenericOperator_Op(const GenericOperator_Op& op)
-        : Operator(op.type().c_str()), mParams(op.mParams), mNbDataIn(op.mNbDataIn), mNbIn(op.mNbIn), mNbOut(op.mNbOut)
+        : Operator(op.type().c_str()), mNbDataIn(op.mNbDataIn), mNbIn(op.mNbIn), mNbOut(op.mNbOut)
     {
         // cpy-ctor
         mInputs = std::vector<std::shared_ptr<Tensor>>(mNbIn);
@@ -79,41 +79,6 @@ class GenericOperator_Op
         return std::make_shared<GenericOperator_Op>(*this);
     }
 
-    /**
-     * @brief Get the Parameter object identified by its name.
-     * @tparam T expected parameter type.
-     * @param key Parameter name.
-     * @details assert if T is not the actual parameter type, if the parameter
-     * does not exist or internal parameter position is invalid.
-     * @todo Returning a T const& ? But dangerous => may get an address within
-     * param buffer that will get invalid after the CParam death.
-     * @note at() throws if the parameter does not exist, using find to test
-     * for parameter existance
-     * @return template<class T> The parameter.
-     */
-    template <class T>
-    const T& getParameter(std::string const &key) const {
-        return mParams.Get<const T>(key);
-    }
-
-    template <class T>
-    T& getParameter(std::string const &key) {
-        return mParams.Get<T>(key);
-    }
-
-    ///\brief Add a parameter value, identified by its name
-    ///\tparam T expected parameter type
-    ///\param i_ParamName Parameter name
-    ///\param i_Value Parameter value
-    ///\todo Pass i_Value by ref if large or not trivial
-    ///\bug If parameter already exists, its value is changed but written in the
-    /// internal buffer in a new location (previous value is still in memory at
-    /// its previous location)
-    template <class T>
-    void addParameter(std::string const &key, T&& value) {
-        mParams.Add<T>(key, std::forward<T>(value));
-    }
-
     // Helper functions that can be used with setComputeOutputDims():
     static const ComputeDimsFunc Identity;
 
@@ -121,10 +86,6 @@ class GenericOperator_Op
         mComputeOutputDims = func;
     }
 
-    std::string getParameterType(std::string const &key) { return mParams.getParamType(key); }
-
-    std::vector<std::string> getParametersName() { return mParams.getParametersName(); }
-
     // Override Virtual Opertor methods
     void associateInput(const IOIndex_t inputIdx, std::shared_ptr<Data> data) override final {
         assert(inputIdx < mNbIn && "operators supports only x inputs");
diff --git a/include/aidge/operator/LeakyReLU.hpp b/include/aidge/operator/LeakyReLU.hpp
index dc9548515134a68ad28a8b58213b536cd43fc406..c6ee01239e1ed065587276c1891d26ba3899fe89 100644
--- a/include/aidge/operator/LeakyReLU.hpp
+++ b/include/aidge/operator/LeakyReLU.hpp
@@ -15,7 +15,7 @@
 #include <vector>
 #include <memory>
 
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
@@ -25,13 +25,13 @@
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class LeakyReLUParam {
+enum class LeakyReLUAttr {
     NegativeSlope
 };
 
 class LeakyReLU_Op : public Operator,
     public Registrable<LeakyReLU_Op, std::string, std::unique_ptr<OperatorImpl>(const LeakyReLU_Op&)>,
-    public Parameterizable<LeakyReLUParam, float> {
+    public StaticAttributes<LeakyReLUAttr, float> {
 public:
     // FIXME: change accessibility
     std::shared_ptr<Tensor> mInput = std::make_shared<Tensor>();
@@ -42,24 +42,24 @@ public:
 
     LeakyReLU_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<LeakyReLUParam, float>;
-    template <LeakyReLUParam e> using param = typename Parameterizable_::template param<e>;
+    using Attributes_ = StaticAttributes<LeakyReLUAttr, float>;
+    template <LeakyReLUAttr e> using attr = typename Attributes_::template attr<e>;
 
     LeakyReLU_Op(float negativeSlope)
             : Operator(Type),
-            Parameterizable_(
-                param<LeakyReLUParam::NegativeSlope>(negativeSlope))
+            Attributes_(
+                attr<LeakyReLUAttr::NegativeSlope>(negativeSlope))
     {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     LeakyReLU_Op(const LeakyReLU_Op& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -147,7 +147,7 @@ inline std::shared_ptr<Node> LeakyReLU(float negativeSlope = 0.0f, const std::st
 
 namespace {
 template <>
-const char* const EnumStrings<Aidge::LeakyReLUParam>::data[]
+const char* const EnumStrings<Aidge::LeakyReLUAttr>::data[]
     = {"NegativeSlope"};
 }
 
diff --git a/include/aidge/operator/MatMul.hpp b/include/aidge/operator/MatMul.hpp
index 9e7e7e43a52afdc93a0f3a0430ae64010abb11dc..d0dadd847a59c9d2a1c0dd97f2f200437da71863 100644
--- a/include/aidge/operator/MatMul.hpp
+++ b/include/aidge/operator/MatMul.hpp
@@ -23,17 +23,17 @@
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/operator/Producer.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 
 namespace Aidge {
-enum class MatMulParam { OutChannels };
+enum class MatMulAttr { OutChannels };
 
 class MatMul_Op : public Operator,
               public Registrable<MatMul_Op,
                                  std::string,
                                  std::unique_ptr<OperatorImpl>(const MatMul_Op &)>,
-              public Parameterizable<MatMulParam, DimSize_t> {
+              public StaticAttributes<MatMulAttr, DimSize_t> {
 public:
     std::array<std::shared_ptr<Tensor>, 2> mInputs = {std::make_shared<Tensor>(), std::make_shared<Tensor>()};
     const std::shared_ptr<Tensor> mOutput = std::make_shared<Tensor>();
@@ -43,24 +43,24 @@ public:
 
     MatMul_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<MatMulParam, DimSize_t>;
-    template <MatMulParam e> using param = typename Parameterizable_::template param<e>;
+    using Attributes_ = StaticAttributes<MatMulAttr, DimSize_t>;
+    template <MatMulAttr e> using attr = typename Attributes_::template attr<e>;
 
     MatMul_Op(DimSize_t out_channels)
             : Operator(Type),
-            Parameterizable_(
-                param<MatMulParam::OutChannels>(out_channels))
+            Attributes_(
+                attr<MatMulAttr::OutChannels>(out_channels))
     {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     MatMul_Op(const MatMul_Op& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -85,9 +85,9 @@ public:
     void computeOutputDims() override final {
         if (!mInputs[0]->empty()) {
             // <in_features**, out_channels>
-            std::array<DimSize_t, 2> weightDims = {this->template get<MatMulParam::OutChannels>(), static_cast<DimSize_t>(mInputs[0]->sizeM1())};
+            std::array<DimSize_t, 2> weightDims = {this->template getAttr<MatMulAttr::OutChannels>(), static_cast<DimSize_t>(mInputs[0]->sizeM1())};
             // <out_channels, batch>
-            std::array<DimSize_t, 2> outputDims = {mInputs[0]->dims()[0], this->template get<MatMulParam::OutChannels>()};
+            std::array<DimSize_t, 2> outputDims = {mInputs[0]->dims()[0], this->template getAttr<MatMulAttr::OutChannels>()};
 
             mInputs[1]->resize(weightDims);
             mOutput->resize(outputDims);
@@ -160,7 +160,7 @@ inline std::shared_ptr<Node> MatMul(DimSize_t out_channels, const std::string& n
 
 namespace {
 template <>
-const char *const EnumStrings<Aidge::MatMulParam>::data[] = {"OutChannels"};
+const char *const EnumStrings<Aidge::MatMulAttr>::data[] = {"OutChannels"};
 }
 
 #endif /* AIDGE_CORE_OPERATOR__MATMUL_H_ */
diff --git a/include/aidge/operator/MaxPooling.hpp b/include/aidge/operator/MaxPooling.hpp
index 775583fd4c2132a5474d136c60c1b53b47ea4c3d..eae7e30df039c0514443e567032427f7a6556360 100644
--- a/include/aidge/operator/MaxPooling.hpp
+++ b/include/aidge/operator/MaxPooling.hpp
@@ -21,17 +21,17 @@
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/operator/Producer.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class MaxPoolingParam { StrideDims, KernelDims, PaddingDims };
+enum class MaxPoolingAttr { StrideDims, KernelDims, PaddingDims };
 
 template <DimIdx_t DIM>
 class MaxPooling_Op : public Operator,
                 public Registrable<MaxPooling_Op<DIM>, std::string, std::unique_ptr<OperatorImpl>(const MaxPooling_Op<DIM> &)>,
-                public Parameterizable<MaxPoolingParam,
+                public StaticAttributes<MaxPoolingAttr,
                                        std::array<DimSize_t, DIM>,
                                        std::array<DimSize_t, DIM>,
                                        std::array<DimSize_t, (DIM<<1) >> {
@@ -45,31 +45,31 @@ public:
 
     MaxPooling_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<MaxPoolingParam,
+    using Attributes_ = StaticAttributes<MaxPoolingAttr,
                                              std::array<DimSize_t, DIM>,
                                              std::array<DimSize_t, DIM>,
                                              std::array<DimSize_t, (DIM<<1)> >;
-    template <MaxPoolingParam e>
-    using param = typename Parameterizable_::template param<e>;
+    template <MaxPoolingAttr e>
+    using attr = typename Attributes_::template attr<e>;
 
     constexpr MaxPooling_Op(const std::array<DimSize_t, DIM> &kernel_dims,
                             const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
                             const std::array<DimSize_t, (DIM<<1)> &padding_dims = create_array<DimSize_t,(DIM<<1)>(0))
         : Operator(Type),
-          Parameterizable_(param<MaxPoolingParam::StrideDims>(stride_dims),
-                           param<MaxPoolingParam::KernelDims>(kernel_dims),
-                           param<MaxPoolingParam::PaddingDims>(padding_dims)),
+          Attributes_(attr<MaxPoolingAttr::StrideDims>(stride_dims),
+                           attr<MaxPoolingAttr::KernelDims>(kernel_dims),
+                           attr<MaxPoolingAttr::PaddingDims>(padding_dims)),
           mOutput(std::make_shared<Tensor>()) {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     MaxPooling_Op(const MaxPooling_Op<DIM>& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -97,13 +97,13 @@ public:
         if (!mInput->empty()) {
             std::array<DimSize_t, DIM + 2> outputDims = {};
 
-            for (std::size_t dim = 0; dim < this->template get<MaxPoolingParam::KernelDims>().size() ; ++dim) {
+            for (std::size_t dim = 0; dim < this->template getAttr<MaxPoolingAttr::KernelDims>().size() ; ++dim) {
                 outputDims[dim+2] = 1 + static_cast<DimSize_t>(
                                             std::floor(static_cast<float>(mInput->dims()[dim+2] -
-                                                                    this->template get<MaxPoolingParam::KernelDims>()[dim] +
-                                                                    this->template get<MaxPoolingParam::PaddingDims>()[dim] +
-                                                                    this->template get<MaxPoolingParam::PaddingDims>()[dim+DIM]) /
-                                            static_cast<float>(this->template get<MaxPoolingParam::StrideDims>()[dim])));
+                                                                    this->template getAttr<MaxPoolingAttr::KernelDims>()[dim] +
+                                                                    this->template getAttr<MaxPoolingAttr::PaddingDims>()[dim] +
+                                                                    this->template getAttr<MaxPoolingAttr::PaddingDims>()[dim+DIM]) /
+                                            static_cast<float>(this->template getAttr<MaxPoolingAttr::StrideDims>()[dim])));
             }
             outputDims[1] = mInput->dims()[1];
             outputDims[0] = mInput->dims()[0];
@@ -190,7 +190,7 @@ inline std::shared_ptr<Node> MaxPooling(
 
 namespace {
 template <>
-const char *const EnumStrings<Aidge::MaxPoolingParam>::data[] = {"StrideDims", "KernelDims", "PaddingDims"};
+const char *const EnumStrings<Aidge::MaxPoolingAttr>::data[] = {"StrideDims", "KernelDims", "PaddingDims"};
 }
 
 #endif /* AIDGE_CORE_OPERATOR_MAXPOOLING_H_ */
diff --git a/include/aidge/operator/MetaOperator.hpp b/include/aidge/operator/MetaOperator.hpp
index 327361de4fc278efbf6cc1afe4c140c7994fe61e..0c77a752493d251303c036c4061823c4f8bc499d 100644
--- a/include/aidge/operator/MetaOperator.hpp
+++ b/include/aidge/operator/MetaOperator.hpp
@@ -23,7 +23,7 @@ public:
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     MetaOperator(const MetaOperator& op)
diff --git a/include/aidge/operator/Producer.hpp b/include/aidge/operator/Producer.hpp
index 681ed96892e216094f9392df01f5a10f66609638..593192c9f402e2646ac94cff68aa0c805f5aecd1 100644
--- a/include/aidge/operator/Producer.hpp
+++ b/include/aidge/operator/Producer.hpp
@@ -19,7 +19,7 @@
 #include "aidge/data/Tensor.hpp"
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/Operator.hpp"
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 
 namespace Aidge {
@@ -51,7 +51,7 @@ public:
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     Producer_Op(const Producer_Op& op)
diff --git a/include/aidge/operator/ReLU.hpp b/include/aidge/operator/ReLU.hpp
index cebfa5718886ec26871462f48edcdbc28117da59..433e353f05f8b4ffc3cfc0e047464e7f9257da02 100644
--- a/include/aidge/operator/ReLU.hpp
+++ b/include/aidge/operator/ReLU.hpp
@@ -43,7 +43,7 @@ public:
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     ReLU_Op(const ReLU_Op& op)
diff --git a/include/aidge/operator/Scaling.hpp b/include/aidge/operator/Scaling.hpp
index e3cba81a490d3b4b28dd3754df7d274eb2e3519a..0ea6ba39b3e4def2011ae5c7b2b9c348df5e2929 100644
--- a/include/aidge/operator/Scaling.hpp
+++ b/include/aidge/operator/Scaling.hpp
@@ -17,7 +17,7 @@
 
 
 
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
@@ -27,13 +27,13 @@
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class ScalingParam {
+enum class ScalingAttr {
     scalingFactor
 };
 
 class Scaling_Op : public Operator,
     public Registrable<Scaling_Op, std::string, std::unique_ptr<OperatorImpl>(const Scaling_Op&)>,
-    public Parameterizable<ScalingParam, float> {
+    public StaticAttributes<ScalingAttr, float> {
 public:
     // FIXME: change accessibility
     std::shared_ptr<Tensor> mInput = std::make_shared<Tensor>();
@@ -44,24 +44,24 @@ public:
 
     Scaling_Op() = delete;
 
-    using Parameterizable_ = Parameterizable<ScalingParam, float>;
-    template <ScalingParam e> using param = typename Parameterizable_::template param<e>;
+    using Attributes_ = StaticAttributes<ScalingAttr, float>;
+    template <ScalingAttr e> using attr = typename Attributes_::template attr<e>;
 
     Scaling_Op(float scalingFactor)
             : Operator(Type),
-            Parameterizable_(
-                param<ScalingParam::scalingFactor>(scalingFactor))
+            Attributes_(
+                attr<ScalingAttr::scalingFactor>(scalingFactor))
     {
         setDatatype(DataType::Float32);
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     Scaling_Op(const Scaling_Op& op)
         : Operator(Type),
-          Parameterizable_(op),
+          Attributes_(op),
           mOutput(std::make_shared<Tensor>(*op.mOutput))
     {
         // cpy-ctor
@@ -155,7 +155,7 @@ inline std::shared_ptr<Node> Scaling(float scalingFactor = 1.0f, const std::stri
 
 namespace {
 template <>
-const char* const EnumStrings<Aidge::ScalingParam>::data[]
+const char* const EnumStrings<Aidge::ScalingAttr>::data[]
     = {"scalingFactor"};
 }
 
diff --git a/include/aidge/operator/Softmax.hpp b/include/aidge/operator/Softmax.hpp
index ffaf0001fbaadf7dc700fca43d77b9998ab26eb2..898bae4c31bb2c41947523a86bfb9cd5c7b732b4 100644
--- a/include/aidge/operator/Softmax.hpp
+++ b/include/aidge/operator/Softmax.hpp
@@ -43,7 +43,7 @@ public:
     }
 
     /**
-     * @brief Copy-constructor. Copy the operator parameters and its output tensor(s), but not its input tensors (the new operator has no input associated).
+     * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
      * @param op Operator to copy.
      */
     Softmax_Op(const Softmax_Op& op)
diff --git a/include/aidge/utils/Any.hpp b/include/aidge/utils/Any.hpp
index 0310c38ccd855f64c8485a114962738203f03ef5..0e65710596d31920de60a35d600e7ae612ea2bc4 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/Attributes.hpp b/include/aidge/utils/Attributes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..76875f15ff4229522e6208b0edb23ec519ff59ce
--- /dev/null
+++ b/include/aidge/utils/Attributes.hpp
@@ -0,0 +1,76 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+#ifndef AIDGE_CORE_UTILS_ATTRIBUTES_H_
+#define AIDGE_CORE_UTILS_ATTRIBUTES_H_
+
+#ifdef PYBIND
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#endif
+#include <vector>
+#include <string>
+
+#ifdef PYBIND
+namespace py = pybind11;
+#endif
+
+namespace {
+// This is the type that will hold all the strings. Each enumerate type will
+// declare its own specialization.
+template <typename T> struct EnumStrings {
+    static const char* const data[];
+};
+}
+
+namespace Aidge {
+template<class T, std::size_t N>
+constexpr std::size_t size(T (&)[N]) { return N; }
+
+/* This abstract class allows to avoid binding Attributes.
+*  Otherwise we would need to bind every template possible of Attributes.
+*  Every operators can access the methods of this class by inheriting from
+*  Attributes in the binding code.
+*/
+class Attributes {
+public:
+    /**
+     * @brief Check if the attribute exists.
+     * @param name Name of the attribute to check.
+     * @return bool True if the attribute exists, false otherwise.
+    */
+    virtual bool hasAttr(const std::string& name) const = 0;
+
+    /**
+     * @brief Get the (implementation defined) name of the type of an attribute, returned by std::type_info::name.
+     * @param name Name of the attribute.
+     * @return std::string Name of the type as returned by std::type_info::name.
+    */
+    virtual std::string getAttrType(const std::string& name) const = 0;
+
+    /**
+     * @brief Get the attribute's name list.
+     * @return std::set<std::string> Vector of names of the attributes.
+    */
+    virtual std::set<std::string> getAttrsName() const = 0;
+
+#ifdef PYBIND
+    /* Bindable get function, does not recquire any templating.
+    *  This is thanks to py::object which allow the function to
+    *  be agnostic from its return type.
+    */
+    virtual py::object getAttrPy(const std::string& name) const = 0;
+#endif
+    virtual ~Attributes() {}
+};
+}
+
+#endif /* AIDGE_CORE_UTILS_ATTRIBUTES_H_ */
diff --git a/include/aidge/utils/CParameter.hpp b/include/aidge/utils/CParameter.hpp
deleted file mode 100644
index 7246bc3c7555c12402e864f62416b714052320d7..0000000000000000000000000000000000000000
--- a/include/aidge/utils/CParameter.hpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2023 CEA-List
- *
- * 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.
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- ********************************************************************************/
-
-#ifndef AIDGE_CPARAMETER_H_
-#define AIDGE_CPARAMETER_H_
-
-#include <map>
-#include <vector>
-#include <string>
-#include <type_traits>
-#include <typeinfo>
-#include <assert.h>
-
-#include "aidge/utils/Any.hpp"
-
-
-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 CParameter {
-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();
-    }
-public:
-    CParameter() : m_Params({}){};
-    ~CParameter() = default;
-
-    /**
-     * \brief Returning a parameter identified by its name
-     * \tparam T expected parameter type
-     * \param i_ParamName Parameter name
-     * \details assert if T is not the actual parameter type, if the parameter does not
-     *  exist or interna parameter position is invalid.
-     * \todo Returning a T const& ? But dangerous => the client may get an address within
-     *  param buffer that will get invalid after the CParam death.
-     * \note at() throws if the parameter does not exist, using find to test for parameter existance
-     */
-    template<class T> T& Get(const std::string i_ParamName)
-    {
-        return any_cast_ref<T>(m_Buffer[m_Params.at(i_ParamName)]);
-    }
-
-    // template<class T> const T& Get(const std::string i_ParamName) const
-    // {
-    //     return any_cast<T>(m_Buffer[m_Params.at(i_ParamName)]);
-    // }
-
-    ///\brief Add a parameter value, identified by its name
-    ///\tparam T expected parameter type
-    ///\param i_ParamName Parameter name
-    ///\param i_Value Parameter value
-    ///\todo Pass i_Value by ref if large or not trivial
-    ///\bug If parameter already exists, its value is changed but written in the
-    /// internal buffer in a new location (previous value is still in memory at its previous location)
-    template<class T> void Add(const std::string &i_ParamName, T&& i_Value)
-    {
-        m_Params[i_ParamName] = m_Buffer.size(); // Copy pointer offset
-        m_Buffer.push_back(_any(std::forward<T>(i_Value)));
-    }
-
-
-    std::string getParamType(std::string const &i_ParamName){
-        return m_Buffer[m_Params.at(i_ParamName)].type().name();
-    }
-
-    std::vector<std::string> getParametersName(){
-        std::vector<std::string> parametersName;
-        for(auto const& it: m_Params)
-            parametersName.push_back(it.first);
-        return parametersName;
-    }
-
-private:
-    std::map<std::string, std::size_t> m_Params; // { Param name : offset }
-
-    ///\brief All raw pointers to parameters values concatenated. Use custom any class compatible with C++14.
-    std::vector<_any> m_Buffer = {};
-};
-
-}
-
-#endif /* AIDGE_CPARAMETER_H_ */
diff --git a/include/aidge/utils/DynamicAttributes.hpp b/include/aidge/utils/DynamicAttributes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..60f586edf947cef0e139049814263a29b4d01e24
--- /dev/null
+++ b/include/aidge/utils/DynamicAttributes.hpp
@@ -0,0 +1,221 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+#ifndef AIDGE_CORE_UTILS_DYNAMICATTRIBUTES_H_
+#define AIDGE_CORE_UTILS_DYNAMICATTRIBUTES_H_
+
+#include <map>
+#include <vector>
+#include <type_traits>
+#include <typeinfo>
+#include <cassert>
+#include <string>
+
+#include "aidge/utils/Any.hpp"
+#include "aidge/utils/Attributes.hpp"
+
+#ifdef PYBIND
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+#include <pybind11/embed.h>
+
+namespace py = pybind11;
+#endif
+
+
+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 {
+public:
+    /**
+     * \brief Returning an Attribute identified by its name
+     * \tparam T expected Attribute type
+     * \param name Attribute name
+     * \details assert if T is not the actual Attribute type or if the Attribute does not
+     *  exist
+     * \note at() throws if the Attribute does not exist, using find to test for Attribute existance
+     */
+    template<class T> T& getAttr(const std::string& name)
+    {
+#ifdef PYBIND
+        // If attribute does not exist in C++, it might have been created or modified in Python
+        auto it = mAttrs.find(name);
+        if (it == mAttrs.end()) {
+            auto itPy = mAttrsPy.find(name);
+            if (itPy != mAttrsPy.end()) {
+                // Insert the attribute back in C++
+                mAttrs.emplace(std::make_pair(name, libany::any(itPy->second.cast<T>())));
+            }
+        }
+#endif
+
+        return libany::any_cast<T&>(mAttrs.at(name));
+    }
+
+    template<class T> const T& getAttr(const std::string& name) const
+    {
+#ifdef PYBIND
+        // If attribute does not exist in C++, it might have been created or modified in Python
+        auto it = mAttrs.find(name);
+        if (it == mAttrs.end()) {
+            auto itPy = mAttrsPy.find(name);
+            if (itPy != mAttrsPy.end()) {
+                // Insert the attribute back in C++
+                mAttrs.emplace(std::make_pair(name, libany::any(itPy->second.cast<T>())));
+            }
+        }
+#endif
+
+        return libany::any_cast<const T&>(mAttrs.at(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
+    template<class T> void addAttr(const std::string& name, const T& value)
+    {
+        const auto& res = mAttrs.emplace(std::make_pair(name, libany::any(value)));
+        assert(res.second && "attribute already exists");
+
+#ifdef PYBIND
+        // We cannot handle Python object if the Python interpreter is not running
+        if (Py_IsInitialized()) {
+            // Keep a copy of the attribute in py::object that is updated everytime
+            mAttrsPy.emplace(std::make_pair(name, py::cast(value)));
+        }
+#endif
+    }
+
+    ///\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, const T& value)
+    {
+        auto res = mAttrs.emplace(std::make_pair(name, libany::any(value)));
+        if (!res.second)
+            res.first->second = libany::any(value);
+
+#ifdef PYBIND
+        // We cannot handle Python object if the Python interpreter is not running
+        if (Py_IsInitialized()) {
+            // Keep a copy of the attribute in py::object that is updated everytime
+            auto resPy = mAttrsPy.emplace(std::make_pair(name, py::cast(value)));
+            if (!resPy.second)
+                resPy.first->second = std::move(py::cast(value));
+        }
+#endif
+    }
+
+    void delAttr(const std::string& name) {
+        mAttrs.erase(name);
+#ifdef PYBIND
+        mAttrsPy.erase(name);
+#endif
+    }
+
+#ifdef PYBIND
+    void addAttrPy(const std::string& name, py::object&& value)
+    {
+        auto it = mAttrs.find(name);
+        assert(it == mAttrs.end() && "attribute already exists");
+
+        const auto& res = mAttrsPy.emplace(std::make_pair(name, value));
+        assert(res.second && "attribute already exists");
+    }
+
+    void setAttrPy(const std::string& name, py::object&& value)
+    {
+        auto resPy = mAttrsPy.emplace(std::make_pair(name, value));
+        if (!resPy.second)
+            resPy.first->second = std::move(value);
+
+        // Force getAttr() to take attribute value from mAttrsPy and update mAttrs
+        mAttrs.erase(name);
+    }
+#endif
+
+    //////////////////////////////////////
+    ///     Generic Attributes API
+    //////////////////////////////////////
+    bool hasAttr(const std::string& name) const override final {
+#ifdef PYBIND
+        // Attributes might have been created in Python, the second condition is necessary.
+        return (mAttrs.find(name) != mAttrs.end() || mAttrsPy.find(name) != mAttrsPy.end());
+#else
+        return (mAttrs.find(name) != mAttrs.end());
+#endif
+    }
+
+    std::string getAttrType(const std::string& name) const override final {
+        // In order to remain consistent between C++ and Python, with or without PyBind, the name of the type is:
+        // - C-style for C++ created attributes
+        // - Python-style for Python created attributes
+#ifdef PYBIND
+        // If attribute does not exist in C++, it might have been created in Python
+        auto it = mAttrs.find(name);
+        if (it == mAttrs.end()) {
+            auto itPy = mAttrsPy.find(name);
+            if (itPy != mAttrsPy.end()) {
+                return std::string(Py_TYPE(itPy->second.ptr())->tp_name);
+            }
+        }
+#endif
+
+        return mAttrs.at(name).type().name();
+    }
+
+    std::set<std::string> getAttrsName() const override final {
+        std::set<std::string> attrsName;
+        for(auto const& it: mAttrs)
+            attrsName.insert(it.first);
+#ifdef PYBIND
+        // Attributes might have been created in Python
+        for(auto const& it: mAttrsPy)
+            attrsName.insert(it.first);
+#endif
+        return attrsName;
+    }
+
+#ifdef PYBIND
+    /**
+     * @detail See https://github.com/pybind/pybind11/issues/1590 as to why a
+     * generic type caster for std::any is not feasable.
+     * The strategy here is to keep a copy of each attribute in py::object that is updated everytime.
+    */
+    py::object getAttrPy(const std::string& name) const {
+        return mAttrsPy.at(name);
+    };
+#endif
+
+private:
+#ifdef PYBIND
+    // Stores C++ attributes (copy) and Python-only attributes
+    // Code should be compiled with -fvisibility=hidden
+    // See https://pybind11.readthedocs.io/en/stable/faq.html:
+    // “‘SomeClass’ declared with greater visibility than the type of its 
+    // field ‘SomeClass::member’ [-Wattributes]”
+    // This map will only be populated if Python interpreter is running
+    std::map<std::string, py::object> mAttrsPy;
+    // Stores C++ attributes only
+    // mutable because it may be updated in getAttr() from Python
+    mutable std::map<std::string, libany::any> mAttrs;
+#else
+    std::map<std::string, libany::any> mAttrs;
+#endif
+};
+
+}
+
+#endif /* AIDGE_CORE_UTILS_DYNAMICATTRIBUTES_H_ */
diff --git a/include/aidge/utils/Parameter.hpp b/include/aidge/utils/Parameter.hpp
deleted file mode 100644
index 2b48e833533da5b8bb4a5f4f134860e89717804a..0000000000000000000000000000000000000000
--- a/include/aidge/utils/Parameter.hpp
+++ /dev/null
@@ -1,204 +0,0 @@
-/********************************************************************************
- * Copyright (c) 2023 CEA-List
- *
- * 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.
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- ********************************************************************************/
-
-#ifndef AIDGE_CORE_UTILS_PARAMETER_H_
-#define AIDGE_CORE_UTILS_PARAMETER_H_
-
-#ifdef PYBIND
-#include <pybind11/pybind11.h>
-#include <pybind11/stl.h>
-#include <string> // Add this inclue to print error
-#endif
-#include <tuple>
-#include <cassert>
-#include <cstddef>
-
-#ifdef PYBIND
-namespace py = pybind11;
-#endif
-
-namespace {
-// This is the type that will hold all the strings. Each enumerate type will
-// declare its own specialization.
-template <typename T> struct EnumStrings {
-    static const char* const data[];
-};
-}
-
-namespace Aidge {
-template<class T, std::size_t N>
-constexpr std::size_t size(T (&)[N]) { return N; }
-
-#ifdef PYBIND
-/* This abstract class allows to avoid binding Parametrizable.
-*  Otherwise we would need to bind every template possible of Parametrizable.
-*  Every operators can access the methods of this class by inheriting from
-*  PyAbstractParametrizable in the binding code.
-*/
-class PyAbstractParametrizable{
-    public:
-        /* Bindable get function, does not recquire any templating.
-        *  This is thanks to py::object which allow the function to
-        *  be agnostic from its return type.
-        */
-        virtual py::object getPy(const char* /*name*/) = 0;
-};
-#endif
-
-template <class PARAM_ENUM, class ...T>
-class Parameterizable
-#ifdef PYBIND
-    : public PyAbstractParametrizable
-#endif
-    {
-public:
-    using Parameters = std::tuple<T...>;
-
-    // Helper class to pass to the constructor
-    template <PARAM_ENUM paramEnum>
-    class param {
-    public:
-        constexpr param(const typename std::tuple_element<static_cast<std::size_t>(paramEnum),std::tuple<T...>>::type& v) : value(v) {}
-        const typename std::tuple_element<static_cast<std::size_t>(paramEnum),std::tuple<T...>>::type value;
-    };
-
-/*
-    // Direct tuple initialization
-    Parameterizable(T... params) : mParams({params...}) {
-
-    }
-*/
-
-    // Constructor for parameters initialization.
-    // Compile-time garantee that every parameter is initialized.
-    template <PARAM_ENUM ...paramEnum> // non-type parameter pack
-    constexpr Parameterizable(const param<paramEnum>&&... params) {
-        // Check number of params consistency
-        static_assert(sizeof...(params) == std::tuple_size<std::tuple<T...>>::value, "wrong number of parameters in constructor");
-        // static_assert(size(EnumStrings<PARAM_ENUM>::data) == std::tuple_size<std::tuple<T...>>::value, "wrong number of parameters in enum string");
-
-        // Check no duplicates
-        constexpr std::array<PARAM_ENUM, std::tuple_size<std::tuple<T...>>::value> pe = { paramEnum... };
-        static_assert(!hasDuplicates(pe), "duplicate parameter"); // requires C++14
-
-        // Init params with constructor arguments
-        const std::array<PARAM_ENUM, std::tuple_size<std::tuple<T...>>::value> p = { ((void)(get<paramEnum>() = params.value), paramEnum) ... };
-        (void)p; // avoid unused warning
-    }
-
-    Parameterizable(const Parameterizable& params):
-        mParams(params.mParams)
-    {
-        // cpy-ctor (required for Operator cpy-ctor)
-    }
-
-    // Compile-time access with enum
-    template <PARAM_ENUM paramEnum>
-    constexpr typename std::tuple_element<static_cast<std::size_t>(paramEnum),std::tuple<T...>>::type& get() {
-        return std::get<static_cast<std::size_t>(paramEnum)>(mParams);
-    }
-
-    template <PARAM_ENUM paramEnum>
-    constexpr const typename std::tuple_element<static_cast<std::size_t>(paramEnum),std::tuple<T...>>::type& get() const {
-        return std::get<static_cast<std::size_t>(paramEnum)>(mParams);
-    }
-
-    // Runtime access with enum
-    template <typename R>
-    constexpr R& get(PARAM_ENUM paramEnum) {
-        return get<R>(static_cast<std::size_t>(paramEnum));
-    }
-
-    template <typename R>
-    constexpr const R& get(PARAM_ENUM paramEnum) const {
-        return get<R>(static_cast<std::size_t>(paramEnum));
-    }
-
-    // Runtime existance check with name
-    constexpr bool isParam(const char* name) const {
-        for (std::size_t i = 0; i < size(EnumStrings<PARAM_ENUM>::data); ++i) {
-            if (strcmp(EnumStrings<PARAM_ENUM>::data[i], name) == 0) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    // Runtime access with name
-    template <typename R>
-    constexpr R& get(const char* name) {
-        for (std::size_t i = 0; i < size(EnumStrings<PARAM_ENUM>::data); ++i) {
-            if (strcmp(EnumStrings<PARAM_ENUM>::data[i], name) == 0) {
-                return get<R>(i);
-            }
-        }
-
-        assert(false && "parameter not found");
-    }
-
-    template <typename R, std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value>
-    constexpr typename std::enable_if<(SIZE > 0), R&>::type get(std::size_t i) {
-        if (i == SIZE-1) {
-            if (std::is_same<R, typename std::tuple_element<SIZE-1,std::tuple<T...>>::type>::value) {
-                return reinterpret_cast<R&>(std::get<SIZE-1>(mParams));
-            }
-            else {
-                assert(false && "wrong parameter type");
-            }
-        }
-        else {
-            return get<R, SIZE-1>(i);
-        }
-    }
-
-    template <typename R, std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value>
-    [[noreturn]] constexpr typename std::enable_if<(SIZE == 0), R&>::type get(std::size_t /*i*/) {
-        assert(false && "parameter not found");
-        exit(-1);
-    }
-
-    constexpr const std::tuple<T...>& getParams() const {
-        return mParams;
-    }
-
-    #ifdef PYBIND
-    py::object getPy(const char* name){
-        for (std::size_t i = 0; i < size(EnumStrings<PARAM_ENUM>::data); ++i) {
-            if (strcmp(EnumStrings<PARAM_ENUM>::data[i], name) == 0) {
-                // https://github.com/pybind/pybind11/blob/f3e0602802c7840992c97f4960515777cad6a5c7/include/pybind11/pytypes.h#L1119-L1138
-                // Normal accessor would not work has we convert the tuple to a py::object which can be anything
-                return py::detail::accessor_policies::tuple_item::get(py::cast(mParams), static_cast<py::size_t>(i));
-            }
-        }
-        throw py::value_error("Parameter : " + std::string(name) + " does not exist." );
-    };
-    #endif
-
-private:
-    template <typename V, std::size_t N>
-    static constexpr bool hasDuplicates(const std::array<V, N>& array) {
-        for (std::size_t i = 1; i < N; i++) {
-            for (std::size_t j = 0; j < i; j++) {
-                if (array[i] == array[j]) {
-                    return true;
-                }
-            }
-        }
-
-        return false;
-    }
-
-    std::tuple<T...> mParams;
-};
-}
-
-#endif /* AIDGE_CORE_UTILS_PARAMETER_H_ */
diff --git a/include/aidge/utils/StaticAttributes.hpp b/include/aidge/utils/StaticAttributes.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..fb800cffbcff5d4113961f8e62977417336f2cb8
--- /dev/null
+++ b/include/aidge/utils/StaticAttributes.hpp
@@ -0,0 +1,204 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+#ifndef AIDGE_CORE_UTILS_STATICATTRIBUTES_H_
+#define AIDGE_CORE_UTILS_STATICATTRIBUTES_H_
+
+#include <tuple>
+#include <cassert>
+#include <cstddef>
+#include <typeinfo>
+
+#include "aidge/utils/Attributes.hpp"
+#include "aidge/utils/Utils.hpp"
+
+namespace Aidge {
+/**
+ * @brief This class is designed to handle static attributes (i.e. known at compile-time) 
+ * with named accessors, with minimal overhead (the name strings are not stored in each object 
+ * instance and it remains possible to access attribute without overhead at compile-time).
+*/
+template <class ATTRS_ENUM, class ...T>
+class StaticAttributes : public Attributes {
+public:
+    using Attrs = std::tuple<T...>;
+
+    // Helper class to pass to the constructor
+    template <ATTRS_ENUM attrsEnum>
+    class attr {
+    public:
+        constexpr attr(const typename std::tuple_element<static_cast<std::size_t>(attrsEnum),std::tuple<T...>>::type& v) : value(v) {}
+        const typename std::tuple_element<static_cast<std::size_t>(attrsEnum),std::tuple<T...>>::type value;
+    };
+
+/*
+    // Direct tuple initialization
+    StaticAttributes(T... attrs) : mAttrs({attrs...}) {
+
+    }
+*/
+
+    // Constructor for Attributes initialization.
+    // Compile-time garantee that every attribute is initialized.
+    template <ATTRS_ENUM ...attrsEnum> // non-type attribute pack
+    constexpr StaticAttributes(const attr<attrsEnum>&&... attrs) {
+        // Check number of attrs consistency
+        static_assert(sizeof...(attrs) == std::tuple_size<std::tuple<T...>>::value, "wrong number of attributes in constructor");
+        // static_assert(size(EnumStrings<ATTRS_ENUM>::data) == std::tuple_size<std::tuple<T...>>::value, "wrong number of attributes in enum string");
+
+        // Check no duplicates
+        constexpr std::array<ATTRS_ENUM, std::tuple_size<std::tuple<T...>>::value> pe = { attrsEnum... };
+        static_assert(!hasDuplicates(pe), "duplicate attribute"); // requires C++14
+
+        // Init attrs with constructor arguments
+        const std::array<ATTRS_ENUM, std::tuple_size<std::tuple<T...>>::value> p = { ((void)(getAttr<attrsEnum>() = attrs.value), attrsEnum) ... };
+        (void)p; // avoid unused warning
+    }
+
+    // Compile-time access with enum
+    template <ATTRS_ENUM attrsEnum>
+    constexpr typename std::tuple_element<static_cast<std::size_t>(attrsEnum),std::tuple<T...>>::type& getAttr() {
+        return std::get<static_cast<std::size_t>(attrsEnum)>(mAttrs);
+    }
+
+    template <ATTRS_ENUM attrsEnum>
+    constexpr const typename std::tuple_element<static_cast<std::size_t>(attrsEnum),std::tuple<T...>>::type& getAttr() const {
+        return std::get<static_cast<std::size_t>(attrsEnum)>(mAttrs);
+    }
+
+    // Runtime access with enum
+    template <typename R>
+    constexpr R& getAttr(ATTRS_ENUM attrsEnum) {
+        return getAttr<R>(static_cast<std::size_t>(attrsEnum));
+    }
+
+    template <typename R>
+    constexpr const R& getAttr(ATTRS_ENUM attrsEnum) const {
+        return getAttr<R>(static_cast<std::size_t>(attrsEnum));
+    }
+
+    // Runtime access with name
+    template <typename R>
+    constexpr R& getAttr(const char* name) {
+        for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) {
+            if (strcmp(EnumStrings<ATTRS_ENUM>::data[i], name) == 0) {
+                return getAttr<R>(i);
+            }
+        }
+
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "attribute \"%s\" not found", name);
+    }
+
+    template <typename R, std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value>
+    constexpr typename std::enable_if<(SIZE > 0), R&>::type getAttr(std::size_t i) {
+        if (i == SIZE-1) {
+            if (std::is_same<R, typename std::tuple_element<SIZE-1,std::tuple<T...>>::type>::value) {
+                return reinterpret_cast<R&>(std::get<SIZE-1>(mAttrs));
+            }
+            else {
+                AIDGE_THROW_OR_ABORT(std::runtime_error, "wrong type for attribute with index %lu", i);
+            }
+        }
+        else {
+            return getAttr<R, SIZE-1>(i);
+        }
+    }
+
+    template <typename R, std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value>
+    [[noreturn]] constexpr typename std::enable_if<(SIZE == 0), R&>::type getAttr(std::size_t /*i*/) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "attribute not found");
+    }
+
+    template <std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value>
+    constexpr typename std::enable_if<(SIZE > 0), const std::type_info&>::type getAttrType(std::size_t i) const {
+        if (i == SIZE-1) {
+            return typeid(typename std::tuple_element<SIZE-1,std::tuple<T...>>::type);
+        }
+        else {
+            return getAttrType<SIZE-1>(i);
+        }
+    }
+
+    template <std::size_t SIZE = std::tuple_size<std::tuple<T...>>::value>
+    [[noreturn]] constexpr typename std::enable_if<(SIZE == 0), const std::type_info&>::type getAttrType(std::size_t /*i*/) const {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "attribute not found");
+    }
+
+    constexpr const std::tuple<T...>& getStaticAttributes() const {
+        return mAttrs;
+    }
+
+    //////////////////////////////////////
+    ///     Generic Attributes API
+    //////////////////////////////////////
+    // Runtime existance check with name
+    constexpr bool hasAttr(const std::string& name) const override final {
+        for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) {
+            if (name == EnumStrings<ATTRS_ENUM>::data[i]) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    // Runtime type access with name
+    constexpr std::string getAttrType(const std::string& name) const override final {
+        for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) {
+            if (name == EnumStrings<ATTRS_ENUM>::data[i]) {
+                return getAttrType(i).name();
+            }
+        }
+
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "attribute \"%s\" not found", name.c_str());
+    }
+
+    std::set<std::string> getAttrsName() const override final {
+        std::set<std::string> attrsName;
+        for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) {
+            attrsName.insert(EnumStrings<ATTRS_ENUM>::data[i]);
+        }
+        return attrsName;
+    }
+
+    #ifdef PYBIND
+    py::object getAttrPy(const std::string& name) const {
+        for (std::size_t i = 0; i < size(EnumStrings<ATTRS_ENUM>::data); ++i) {
+            if (name == EnumStrings<ATTRS_ENUM>::data[i]) {
+                // https://github.com/pybind/pybind11/blob/f3e0602802c7840992c97f4960515777cad6a5c7/include/pybind11/pytypes.h#L1119-L1138
+                // Normal accessor would not work has we convert the tuple to a py::object which can be anything
+                return py::detail::accessor_policies::tuple_item::get(py::cast(mAttrs), static_cast<py::size_t>(i));
+            }
+        }
+
+        AIDGE_THROW_OR_ABORT(py::value_error, "attribute \"%s\" not found", name.c_str());
+    };
+    #endif
+
+private:
+    template <typename V, std::size_t N>
+    static constexpr bool hasDuplicates(const std::array<V, N>& array) {
+        for (std::size_t i = 1; i < N; i++) {
+            for (std::size_t j = 0; j < i; j++) {
+                if (array[i] == array[j]) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    std::tuple<T...> mAttrs;
+};
+}
+
+#endif /* AIDGE_CORE_UTILS_STATICATTRIBUTES_H_ */
diff --git a/include/aidge/utils/Utils.hpp b/include/aidge/utils/Utils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..7c0c03c82ff252b6175d3c9bbe5395bb05127c9f
--- /dev/null
+++ b/include/aidge/utils/Utils.hpp
@@ -0,0 +1,37 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+
+#ifndef AIDGE_UTILS_H_
+#define AIDGE_UTILS_H_
+
+#include <cstdio>
+
+#ifdef NO_EXCEPTIONS
+#define AIDGE_THROW_OR_ABORT(ex, ...) \
+do { std::printf(__VA_ARGS__); std::abort(); } while (false)
+#else
+#include <stdexcept>
+#define AIDGE_THROW_OR_ABORT(ex, ...) \
+do { \
+    int n = 128; \
+    std::unique_ptr<char[]> formatted; \
+    formatted.reset(new char[n]); \
+    const int len = std::snprintf(formatted.get(), n, __VA_ARGS__); \
+    if (len >= n) { \
+        formatted.reset(new char[len + 1]); \
+        std::snprintf(formatted.get(), len + 1, __VA_ARGS__); \
+    }; \
+    throw ex(formatted.get()); \
+} while (false)
+#endif
+
+#endif //AIDGE_UTILS_H_
\ No newline at end of file
diff --git a/python_binding/operator/pybind_Add.cpp b/python_binding/operator/pybind_Add.cpp
index 3efcf7c5345bbc835aeaf6dcbc416769b8654439..ab8b4cf7b91d5eea2db5245a8c5122ab004b4766 100644
--- a/python_binding/operator/pybind_Add.cpp
+++ b/python_binding/operator/pybind_Add.cpp
@@ -12,7 +12,6 @@
 #include <pybind11/pybind11.h>
 
 #include "aidge/operator/Add.hpp"
-#include "aidge/utils/Parameter.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/utils/Types.h"
diff --git a/python_binding/operator/pybind_AvgPooling.cpp b/python_binding/operator/pybind_AvgPooling.cpp
index ecbb743d33cc5750bc60aeed8e5207dcec0c23dc..372afebdd3e1626cd0af88e335b78ec7fd73a5f4 100644
--- a/python_binding/operator/pybind_AvgPooling.cpp
+++ b/python_binding/operator/pybind_AvgPooling.cpp
@@ -16,7 +16,6 @@
 #include <vector>
 #include <array>
 
-#include "aidge/utils/Parameter.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/operator/AvgPooling.hpp"
 #include "aidge/operator/Operator.hpp"
@@ -27,7 +26,7 @@ namespace py = pybind11;
 namespace Aidge {
 
 template <DimIdx_t DIM> void declare_AvgPoolingOp(py::module &m) {
-  py::class_<AvgPooling_Op<DIM>, std::shared_ptr<AvgPooling_Op<DIM>>, Operator, PyAbstractParametrizable>(
+  py::class_<AvgPooling_Op<DIM>, std::shared_ptr<AvgPooling_Op<DIM>>, Operator, Attributes>(
     m, ("AvgPoolingOp" + std::to_string(DIM) + "D").c_str(),
     py::multiple_inheritance())
   .def(py::init<const std::array<DimSize_t, DIM> &,
diff --git a/python_binding/operator/pybind_BatchNorm.cpp b/python_binding/operator/pybind_BatchNorm.cpp
index 70d9bce003033e1264ac39764271773fa84c760f..f43381fecc689a292e166c4da40ea0cb4842c9e6 100644
--- a/python_binding/operator/pybind_BatchNorm.cpp
+++ b/python_binding/operator/pybind_BatchNorm.cpp
@@ -14,7 +14,6 @@
 
 #include "aidge/operator/BatchNorm.hpp"
 #include "aidge/operator/Operator.hpp"
-#include "aidge/utils/Parameter.hpp"
 #include "aidge/utils/Types.h"
 
 namespace py = pybind11;
@@ -22,7 +21,7 @@ namespace Aidge {
 
 template <DimSize_t DIM>
 void declare_BatchNormOp(py::module& m) {
-    py::class_<BatchNorm_Op<DIM>, std::shared_ptr<BatchNorm_Op<DIM>>, Operator, PyAbstractParametrizable>(m, ("BatchNorm_Op" + std::to_string(DIM) + "D").c_str(), py::multiple_inheritance());
+    py::class_<BatchNorm_Op<DIM>, std::shared_ptr<BatchNorm_Op<DIM>>, Operator, Attributes>(m, ("BatchNorm_Op" + std::to_string(DIM) + "D").c_str(), py::multiple_inheritance());
 
     m.def(("BatchNorm" + std::to_string(DIM) + "D").c_str(), &BatchNorm<DIM>, py::arg("epsilon") = 1.0e-5F, py::arg("momentum") = 0.1F, py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_Conv.cpp b/python_binding/operator/pybind_Conv.cpp
index 7e366305f287e958ea7500695c1f3285908017b1..0c09917d71e520227eed48705527adaf204857ee 100644
--- a/python_binding/operator/pybind_Conv.cpp
+++ b/python_binding/operator/pybind_Conv.cpp
@@ -16,7 +16,6 @@
 #include <vector>
 #include <array>
 
-#include "aidge/utils/Parameter.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/operator/Conv.hpp"
 #include "aidge/operator/Operator.hpp"
@@ -26,7 +25,7 @@ namespace py = pybind11;
 namespace Aidge {
 
 template <DimIdx_t DIM> void declare_ConvOp(py::module &m) {
-  py::class_<Conv_Op<DIM>, std::shared_ptr<Conv_Op<DIM>>, Operator, PyAbstractParametrizable>(
+  py::class_<Conv_Op<DIM>, std::shared_ptr<Conv_Op<DIM>>, Operator, Attributes>(
     m, ("ConvOp" + std::to_string(DIM) + "D").c_str(),
     py::multiple_inheritance())
   .def(py::init<DimSize_t,
diff --git a/python_binding/operator/pybind_ConvDepthWise.cpp b/python_binding/operator/pybind_ConvDepthWise.cpp
index 8a81e7ba184536cbd535db24519495400bce6fdb..3f48c50f7ffdb44450c0e2a155d85dcbf9f73fd9 100644
--- a/python_binding/operator/pybind_ConvDepthWise.cpp
+++ b/python_binding/operator/pybind_ConvDepthWise.cpp
@@ -16,7 +16,6 @@
 #include <vector>
 #include <array>
 
-#include "aidge/utils/Parameter.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/operator/ConvDepthWise.hpp"
 #include "aidge/operator/Operator.hpp"
@@ -27,7 +26,7 @@ namespace py = pybind11;
 namespace Aidge {
 
 template <DimIdx_t DIM> void declare_ConvDepthWiseOp(py::module &m) {
-  py::class_<ConvDepthWise_Op<DIM>, std::shared_ptr<ConvDepthWise_Op<DIM>>, Operator, PyAbstractParametrizable>(
+  py::class_<ConvDepthWise_Op<DIM>, std::shared_ptr<ConvDepthWise_Op<DIM>>, Operator, Attributes>(
     m, ("ConvDepthWiseOp" + std::to_string(DIM) + "D").c_str(),
     py::multiple_inheritance())
   .def(py::init<const std::array<DimSize_t, DIM> &,
diff --git a/python_binding/operator/pybind_FC.cpp b/python_binding/operator/pybind_FC.cpp
index 3b4137c6f208f96d256c72300437cc978658b84f..4b9d61d082ebed4d426b41efa071d3943f83d231 100644
--- a/python_binding/operator/pybind_FC.cpp
+++ b/python_binding/operator/pybind_FC.cpp
@@ -12,7 +12,6 @@
 #include <pybind11/pybind11.h>
 
 #include "aidge/operator/FC.hpp"
-#include "aidge/utils/Parameter.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/utils/Types.h"
@@ -21,7 +20,7 @@ namespace py = pybind11;
 namespace Aidge {
 
 void declare_FC(py::module &m) {
-  py::class_<FC_Op, std::shared_ptr<FC_Op>, Operator, PyAbstractParametrizable>(m, "FC_Op", py::multiple_inheritance());
+  py::class_<FC_Op, std::shared_ptr<FC_Op>, Operator, Attributes>(m, "FC_Op", py::multiple_inheritance());
 
   m.def("FC", &FC, py::arg("out_channels"), py::arg("nobias") = false, py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_GenericOperator.cpp b/python_binding/operator/pybind_GenericOperator.cpp
index dfd2cfedec5aa291f11cf7c2a93d750c3d91145f..4cf4dae2234900722058d6555582c5b78900ab7d 100644
--- a/python_binding/operator/pybind_GenericOperator.cpp
+++ b/python_binding/operator/pybind_GenericOperator.cpp
@@ -21,50 +21,11 @@ namespace py = pybind11;
 namespace Aidge {
 
 void init_GenericOperator(py::module& m) {
-    py::class_<GenericOperator_Op, std::shared_ptr<GenericOperator_Op>, Operator>(m, "GenericOperatorOp",
+    py::class_<GenericOperator_Op, std::shared_ptr<GenericOperator_Op>, Operator, DynamicAttributes>(m, "GenericOperatorOp",
                                                                                   py::multiple_inheritance())
-    .def("get_parameter_type", &GenericOperator_Op::getParameterType)
-    .def("get_parameters_name", &GenericOperator_Op::getParametersName)
-    .def("add_parameter", &GenericOperator_Op::addParameter<bool>)
-    .def("add_parameter", &GenericOperator_Op::addParameter<int>)
-    .def("add_parameter", &GenericOperator_Op::addParameter<float>)
-    .def("add_parameter", &GenericOperator_Op::addParameter<std::string>)
-    .def("add_parameter", &GenericOperator_Op::addParameter<std::vector<bool>>)
-    .def("add_parameter", &GenericOperator_Op::addParameter<std::vector<int>>)
-    .def("add_parameter", &GenericOperator_Op::addParameter<std::vector<float>>)
-    .def("add_parameter", &GenericOperator_Op::addParameter<std::vector<std::string>>)
-    .def("get_parameter", [](GenericOperator_Op& self, std::string key) -> py::object {
-        /*
-        This getParameter method returns the good python type without having to have
-        prior knowledge of the parameter type.
-        */
-        py::object res = py::none();
-        std::string paramType = self.getParameterType(key);
-        if(paramType == typeid(int).name())
-            res = py::cast(self.getParameter<int>(key));
-        else if(paramType == typeid(float).name())
-            res = py::cast(self.getParameter<float>(key));
-        else if(paramType == typeid(bool).name())
-            res = py::cast(self.getParameter<bool>(key));
-        else if(paramType == typeid(std::string).name())
-            res = py::cast(self.getParameter<std::string>(key));
-        else if(paramType == typeid(std::vector<bool>).name())
-            res = py::cast(self.getParameter<std::vector<bool>>(key));
-        else if(paramType == typeid(std::vector<int>).name())
-            res = py::cast(self.getParameter<std::vector<int>>(key));
-        else if(paramType == typeid(std::vector<float>).name())
-            res = py::cast(self.getParameter<std::vector<float>>(key));
-        else if(paramType == typeid(std::vector<std::string>).name())
-            res = py::cast(self.getParameter<std::vector<std::string>>(key));
-        else {
-            throw py::key_error("Failed to convert parameter type " + key + ", this issue may come from typeid function which gave an unknown key : [" + paramType + "]. Please open an issue asking to add the support for this key.");
-        }
-        return res;
-    })
     .def_readonly_static("identity", &GenericOperator_Op::Identity)
     .def("compute_output_dims", &GenericOperator_Op::computeOutputDims)
-    .def("set_compute_output_dims", &GenericOperator_Op::setComputeOutputDims, py::arg("computation_function"))
-    ;
+    .def("set_compute_output_dims", &GenericOperator_Op::setComputeOutputDims, py::arg("computation_function"));
 
     m.def("GenericOperator", &GenericOperator, py::arg("type"), py::arg("nbDataIn"), py::arg("nbIn"), py::arg("nbOut"),
           py::arg("name") = "");
diff --git a/python_binding/operator/pybind_LeakyReLU.cpp b/python_binding/operator/pybind_LeakyReLU.cpp
index c062d93f5c40fe46336fe34f6d1664f24da07732..cae8a88bab7b59189dfbc6528cd653f1c97cb73a 100644
--- a/python_binding/operator/pybind_LeakyReLU.cpp
+++ b/python_binding/operator/pybind_LeakyReLU.cpp
@@ -13,13 +13,12 @@
 
 #include "aidge/operator/LeakyReLU.hpp"
 #include "aidge/operator/Operator.hpp"
-#include "aidge/utils/Parameter.hpp"
 
 namespace py = pybind11;
 namespace Aidge {
 
 void init_LeakyReLU(py::module& m) {
-    py::class_<LeakyReLU_Op, std::shared_ptr<LeakyReLU_Op>, Operator, PyAbstractParametrizable>(m, "LeakyReLU_Op", py::multiple_inheritance());
+    py::class_<LeakyReLU_Op, std::shared_ptr<LeakyReLU_Op>, Operator, Attributes>(m, "LeakyReLU_Op", py::multiple_inheritance());
 
     m.def("LeakyReLU", &LeakyReLU, py::arg("negative_slope") = 0.0f, py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_Matmul.cpp b/python_binding/operator/pybind_Matmul.cpp
index b0b3c3df6dfbd2c50969da40c2621dbbdf04178b..2f738550041bcdb1ae809d68fa24fdf5a72e9164 100644
--- a/python_binding/operator/pybind_Matmul.cpp
+++ b/python_binding/operator/pybind_Matmul.cpp
@@ -12,7 +12,6 @@
 #include <pybind11/pybind11.h>
 
 #include "aidge/operator/MatMul.hpp"
-#include "aidge/utils/Parameter.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/utils/Types.h"
@@ -21,7 +20,7 @@ namespace py = pybind11;
 namespace Aidge {
 
 void declare_MatMul(py::module &m) {
-  py::class_<MatMul_Op, std::shared_ptr<MatMul_Op>, Operator, PyAbstractParametrizable>(m, "MatMul_Op", py::multiple_inheritance());
+  py::class_<MatMul_Op, std::shared_ptr<MatMul_Op>, Operator, Attributes>(m, "MatMul_Op", py::multiple_inheritance());
 
   m.def("MatMul", &MatMul, py::arg("out_channels"), py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_MaxPooling.cpp b/python_binding/operator/pybind_MaxPooling.cpp
index 9bd951c446e080ff27b099527ac9bbc350646140..2efd18c816c2d588e574872b3d3776a3409dc4ba 100644
--- a/python_binding/operator/pybind_MaxPooling.cpp
+++ b/python_binding/operator/pybind_MaxPooling.cpp
@@ -16,7 +16,6 @@
 #include <vector>
 #include <array>
 
-#include "aidge/utils/Parameter.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/operator/MaxPooling.hpp"
 #include "aidge/operator/Operator.hpp"
@@ -27,7 +26,7 @@ namespace py = pybind11;
 namespace Aidge {
 
 template <DimIdx_t DIM> void declare_MaxPoolingOp(py::module &m) {
-  py::class_<MaxPooling_Op<DIM>, std::shared_ptr<MaxPooling_Op<DIM>>, Operator, PyAbstractParametrizable>(
+  py::class_<MaxPooling_Op<DIM>, std::shared_ptr<MaxPooling_Op<DIM>>, Operator, Attributes>(
     m, ("MaxPoolingOp" + std::to_string(DIM) + "D").c_str(),
     py::multiple_inheritance())
   .def(py::init<const std::array<DimSize_t, DIM> &,
diff --git a/python_binding/operator/pybind_Producer.cpp b/python_binding/operator/pybind_Producer.cpp
index 4714e096fba90aff5c4289c1f87486e411c21b78..1c62cd0adf6b8712073ec0674754ce7c8c2014a5 100644
--- a/python_binding/operator/pybind_Producer.cpp
+++ b/python_binding/operator/pybind_Producer.cpp
@@ -13,7 +13,6 @@
 #include <pybind11/stl.h>
 
 #include "aidge/utils/Types.h"
-#include "aidge/utils/Parameter.hpp"
 // #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/operator/Operator.hpp"
 #include "aidge/operator/Producer.hpp"
diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp
index db116d132ec8ffc504b2c0910eafc1a3da34534f..d1287c0a928ae2ad27a839cec1c3d3955da65538 100644
--- a/python_binding/pybind_core.cpp
+++ b/python_binding/pybind_core.cpp
@@ -17,7 +17,7 @@ namespace Aidge {
 void init_Data(py::module&);
 void init_Tensor(py::module&);
 void init_OperatorImpl(py::module&);
-void init_Parameterizable(py::module&);
+void init_Attributes(py::module&);
 void init_Operator(py::module&);
 
 void init_Add(py::module&);
@@ -65,7 +65,7 @@ void init_Aidge(py::module& m){
     init_Connector(m);
 
     init_OperatorImpl(m);
-    init_Parameterizable(m);
+    init_Attributes(m);
     init_Operator(m);
     init_Add(m);
     init_AvgPooling(m);
diff --git a/python_binding/utils/pybind_Parameter.cpp b/python_binding/utils/pybind_Parameter.cpp
index 358316ea00413813d6d482a8a4601e69af3aa992..2957876f31ad0781a36905cef3a5ae88934b6a8a 100644
--- a/python_binding/utils/pybind_Parameter.cpp
+++ b/python_binding/utils/pybind_Parameter.cpp
@@ -1,12 +1,36 @@
 #include <pybind11/pybind11.h>
-#include "aidge/utils/Parameter.hpp"
+#include "aidge/utils/Attributes.hpp"
+#include "aidge/utils/DynamicAttributes.hpp"
 
 namespace py = pybind11;
 namespace Aidge {
-void init_Parameterizable(py::module& m){
-    py::class_<PyAbstractParametrizable, std::shared_ptr<PyAbstractParametrizable>>(m, "PyAbstractParametrizable")
-    .def("get", &PyAbstractParametrizable::getPy, py::arg("name"))
-    ;
+DynamicAttributes test_DynamicAttributes_binding() {
+    DynamicAttributes attrs;
+    attrs.addAttr<int>("a", 42);
+    attrs.addAttr<std::string>("b", "test");
+    attrs.addAttr<std::vector<bool>>("c", {true, false, true});
+    return attrs;
 }
+
+double test_DynamicAttributes_binding_check(DynamicAttributes& attrs) {
+    return attrs.getAttr<double>("d");
+}
+
+void init_Attributes(py::module& m){
+    py::class_<Attributes, std::shared_ptr<Attributes>>(m, "Attributes")
+    .def("has_attr", &Attributes::hasAttr, py::arg("name"))
+    .def("get_attr_type", &Attributes::getAttrType, py::arg("name"))
+    .def("get_attrs_name", &Attributes::getAttrsName)
+    .def("get_attr", &Attributes::getAttrPy, py::arg("name"));
+
+    py::class_<DynamicAttributes, std::shared_ptr<DynamicAttributes>, Attributes>(m, "DynamicAttributes")
+    .def("add_attr", &DynamicAttributes::addAttrPy, py::arg("name"), py::arg("value"))
+    .def("set_attr", &DynamicAttributes::setAttrPy, py::arg("name"), py::arg("value"))
+    .def("del_attr", &DynamicAttributes::delAttr, py::arg("name"));
+
+    m.def("test_DynamicAttributes_binding", &test_DynamicAttributes_binding);
+    m.def("test_DynamicAttributes_binding_check", &test_DynamicAttributes_binding_check, py::arg("attrs"));
+}
+
 }
 
diff --git a/src/graphmatching/NodeRegex.cpp b/src/graphmatching/NodeRegex.cpp
index bbb116d1b12a31b491b26d2a64d04b416b61c6b7..9bf164f60255c17492e528b0f27dec8c53f74979 100644
--- a/src/graphmatching/NodeRegex.cpp
+++ b/src/graphmatching/NodeRegex.cpp
@@ -12,7 +12,7 @@
 #include "aidge/graphmatching/NodeRegex.hpp"
 
 
-// Verification done by the Parameter system
+// Verification done by the Attribute system
 
 
 // Version 1 - Only test the type of the node (no need for a lexer)
@@ -39,8 +39,8 @@ bool Aidge::NodeRegex::isA(std::string NodeType){
 /**bool NodeRegex::_is(string &Node_op){
     // Parsing the condition is done in the initialization of the NodeRegex
     
-    // assert parameters exist in the node with the parameter function isParam()
+    // assert attributes exist in the node with the attribute function hasAttr()
 
-    // get the parameters
+    // get the attributes
 
 }*/
diff --git a/src/recipies/FuseBatchNorm.cpp b/src/recipies/FuseBatchNorm.cpp
index 2250bf87c66ff12b702bb2f01c7429c19ed41606..3a50ec3e7f83517267ef4ad04cb2c855f8f9df7e 100644
--- a/src/recipies/FuseBatchNorm.cpp
+++ b/src/recipies/FuseBatchNorm.cpp
@@ -50,8 +50,8 @@ void Aidge::fuseBatchNorm(std::set<std::shared_ptr<Node>> nodes){
 
 
     // TODO : Find a way to remove the template
-    const float epsilon = std::static_pointer_cast<BatchNorm_Op<2>>(batchnorm->getOperator())->get<float>("Epsilon");
-    DimSize_t convOutDims = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->get<DimSize_t>("OutChannels");
+    const float epsilon = std::static_pointer_cast<BatchNorm_Op<2>>(batchnorm->getOperator())->getAttr<float>("Epsilon");
+    DimSize_t convOutDims = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->getAttr<DimSize_t>("OutChannels");
 
 
     assert(scale->size()  == convOutDims);
@@ -79,10 +79,10 @@ void Aidge::fuseBatchNorm(std::set<std::shared_ptr<Node>> nodes){
         printf("variance < 1e-12 for all outputs! Is the network correctly trained?\n");
     }
 
-    const DimSize_t channelsSize = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->get<DimSize_t>("InChannels");
+    const DimSize_t channelsSize = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->getAttr<DimSize_t>("InChannels");
 
     // TODO : suppose we have Conv2D ...
-    const std::array<DimSize_t, 2> kernelDims = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->get<std::array<DimSize_t, 2>>("KernelDims");
+    const std::array<DimSize_t, 2> kernelDims = std::static_pointer_cast<Conv_Op<2>>(conv->getOperator())->getAttr<std::array<DimSize_t, 2>>("KernelDims");
 
     std::shared_ptr<Tensor> weight  = conv->input(1).first->getOperator()->getOutput(conv->input(1).second);
     std::shared_ptr<Tensor> bias  = conv->input(2).first->getOperator()->getOutput(conv->input(2).second);
diff --git a/src/recipies/LabelGraph.cpp b/src/recipies/LabelGraph.cpp
index 7ac2cbf6ca65c7ecbced9596efb71c2052405984..369336f7981198f962d8ab949309005be9ac5eb9 100644
--- a/src/recipies/LabelGraph.cpp
+++ b/src/recipies/LabelGraph.cpp
@@ -22,7 +22,7 @@ Aidge::NodePtr Aidge::nodeLabel(NodePtr node) {
     if (node->type() == Conv_Op<2>::Type) {
         auto op = std::dynamic_pointer_cast<Conv_Op<2>>(node->getOperator());
 
-        auto newOp = std::make_shared<MaxPooling_Op<2>>(op->get<ConvParam::KernelDims>(), op->get<ConvParam::StrideDims>());
+        auto newOp = std::make_shared<MaxPooling_Op<2>>(op->getAttr<ConvAttr::KernelDims>(), op->getAttr<ConvAttr::StrideDims>());
         return std::make_shared<Node>(newOp, node->name());
     }
 
@@ -30,7 +30,7 @@ Aidge::NodePtr Aidge::nodeLabel(NodePtr node) {
     if (node->type() == ConvDepthWise_Op<2>::Type) {
         auto op = std::dynamic_pointer_cast<ConvDepthWise_Op<2>>(node->getOperator());
 
-        auto newOp = std::make_shared<MaxPooling_Op<2>>(op->get<ConvDepthWiseParam::KernelDims>(), op->get<ConvDepthWiseParam::StrideDims>());
+        auto newOp = std::make_shared<MaxPooling_Op<2>>(op->getAttr<ConvDepthWiseAttr::KernelDims>(), op->getAttr<ConvDepthWiseAttr::StrideDims>());
         return std::make_shared<Node>(newOp, node->name());
     }
 
@@ -38,7 +38,7 @@ Aidge::NodePtr Aidge::nodeLabel(NodePtr node) {
     if (node->type() == AvgPooling_Op<2>::Type) {
         auto op = std::dynamic_pointer_cast<AvgPooling_Op<2>>(node->getOperator());
 
-        auto newOp = std::make_shared<MaxPooling_Op<2>>(op->get<AvgPoolingParam::KernelDims>(), op->get<AvgPoolingParam::StrideDims>());
+        auto newOp = std::make_shared<MaxPooling_Op<2>>(op->getAttr<AvgPoolingAttr::KernelDims>(), op->getAttr<AvgPoolingAttr::StrideDims>());
         return std::make_shared<Node>(newOp, node->name());
     }
 
diff --git a/unit_tests/operator/Test_GenericOperator.cpp b/unit_tests/operator/Test_GenericOperator.cpp
index 2208399897f586becca798eb469344af01dbab64..8d634cc3a105c423b54b6003f41204aeb1fc5335 100644
--- a/unit_tests/operator/Test_GenericOperator.cpp
+++ b/unit_tests/operator/Test_GenericOperator.cpp
@@ -17,72 +17,72 @@
 
 using namespace Aidge;
 
-TEST_CASE("[core/operators] GenericOp(add & get parameters)", "[Operator]") {
+TEST_CASE("[core/operators] GenericOp(add & get attributes)", "[Operator]") {
     SECTION("INT") {
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
-        const char* key = "intParam";
-        Testop.addParameter(key, int(5));
-        int registeredVal = Testop.getParameter<int>(key);
+        const char* key = "intAttr";
+        Testop.addAttr(key, int(5));
+        int registeredVal = Testop.getAttr<int>(key);
         REQUIRE(registeredVal == 5);
     }
     SECTION("LONG") {
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
         long value = 3;
-        const char* key = "longParam";
-        Testop.addParameter(key, value);
-        REQUIRE(Testop.getParameter<long>(key) == value);
+        const char* key = "longAttr";
+        Testop.addAttr(key, value);
+        REQUIRE(Testop.getAttr<long>(key) == value);
     }
     SECTION("FLOAT") {
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
         float value = 2.0;
-        const char* key = "floatParam";
-        Testop.addParameter(key, value);
-        REQUIRE(Testop.getParameter<float>(key) == value);
+        const char* key = "floatAttr";
+        Testop.addAttr(key, value);
+        REQUIRE(Testop.getAttr<float>(key) == value);
     }
      SECTION("VECTOR<BOOL>") {
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
         std::vector<bool> value = {true, false, false, true, true};
         const char* key = "vect";
-        Testop.addParameter(key, value);
+        Testop.addAttr(key, value);
 
-        REQUIRE(Testop.getParameter<std::vector<bool>>(key).size() == value.size());
+        REQUIRE(Testop.getAttr<std::vector<bool>>(key).size() == value.size());
         for (std::size_t i=0; i < value.size(); ++i){
-            REQUIRE(Testop.getParameter<std::vector<bool>>(key)[i] == value[i]);
+            REQUIRE(Testop.getAttr<std::vector<bool>>(key)[i] == value[i]);
         }
     }
     SECTION("VECTOR<INT>") {
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
         std::vector<int> value = {1, 2, 3, 4, 5, 6, 7, 8, 9};
         const char* key = "vect";
-        Testop.addParameter(key, value);
+        Testop.addAttr(key, value);
 
-        REQUIRE(Testop.getParameter<std::vector<int>>(key).size() == value.size());
+        REQUIRE(Testop.getAttr<std::vector<int>>(key).size() == value.size());
         for (std::size_t i=0; i < value.size(); ++i){
-            REQUIRE(Testop.getParameter<std::vector<int>>(key)[i] == value[i]);
+            REQUIRE(Testop.getAttr<std::vector<int>>(key)[i] == value[i]);
         }
     }
     SECTION("MULTIPLE PARAMS") {
         /*
-        Goal : Test that the offsets are well done by adding different parameters with different size.
+        Goal : Test that the offsets are well done by adding different attributes with different size.
         */
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
-        Testop.addParameter<long>("longParam", 3);
-        Testop.addParameter<float>("floatParam", 2.0);
-        Testop.addParameter<uint8_t>("uint8Param", 5);
-        Testop.addParameter<long long>("llParam", 10);
-        REQUIRE(Testop.getParameter<long>("longParam") == 3);
-        REQUIRE(Testop.getParameter<float>("floatParam") == 2.0);
-        REQUIRE(Testop.getParameter<uint8_t>("uint8Param") == 5);
-        REQUIRE(Testop.getParameter<long long>("llParam") == 10);
+        Testop.addAttr<long>("longAttr", 3);
+        Testop.addAttr<float>("floatAttr", 2.0);
+        Testop.addAttr<uint8_t>("uint8Attr", 5);
+        Testop.addAttr<long long>("llAttr", 10);
+        REQUIRE(Testop.getAttr<long>("longAttr") == 3);
+        REQUIRE(Testop.getAttr<float>("floatAttr") == 2.0);
+        REQUIRE(Testop.getAttr<uint8_t>("uint8Attr") == 5);
+        REQUIRE(Testop.getAttr<long long>("llAttr") == 10);
     }
 }
 
-TEST_CASE("[core/operator] GenericOp(type check)", "[.ass]") {
+TEST_CASE("[core/operator] GenericOp(type check)", "[Operator]") {
     SECTION("WRONG TYPE FOR GETTER") {
         GenericOperator_Op Testop("TestOp", 1, 1, 1);
-        Testop.addParameter<long>("longParam", 3);
+        Testop.addAttr<long>("longAttr", 3);
 
         // This line should raise a failled assert
-        REQUIRE_THROWS(Testop.getParameter<int>("longParameter"));
+        REQUIRE_THROWS(Testop.getAttr<int>("longAttribute"));
     }
 }
diff --git a/unit_tests/utils/Test_StaticAttributes.cpp b/unit_tests/utils/Test_StaticAttributes.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..36c2e0454b415e1cb25cc3581016530a372b9e65
--- /dev/null
+++ b/unit_tests/utils/Test_StaticAttributes.cpp
@@ -0,0 +1,48 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * 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.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <string>
+#include <vector>
+
+#include "aidge/utils/StaticAttributes.hpp"
+
+using namespace Aidge;
+
+enum class TestAttr { a, b, c, d };
+
+namespace {
+template <>
+const char *const EnumStrings<TestAttr>::data[] = {
+    "a",
+    "b",
+    "c",
+    "d"
+};
+}
+
+using Attributes_ = StaticAttributes<TestAttr, int, float, std::string, std::vector<bool>>;
+template <TestAttr e>
+using attr = typename Attributes_::template attr<e>;
+
+TEST_CASE("[core/attributes] StaticAttribute") {
+    SECTION("TestAttr") {
+        StaticAttributes<TestAttr, int, float, std::string, std::vector<bool>> attrs(
+            attr<TestAttr::a>(42),
+            attr<TestAttr::b>(18.75),
+            attr<TestAttr::c>("test"),
+            attr<TestAttr::d>({true, false, true}));
+
+        REQUIRE(attrs.getAttr<int>("a") == 42);
+        REQUIRE_THROWS(attrs.getAttr<int>("inexistant"));
+    }
+}