diff --git a/include/aidge/operator/ArgMax.hpp b/include/aidge/operator/ArgMax.hpp
index bc97e1f5bdd4dcc80857db55b66e9b6bedb1fa62..5057310d30b118ea6e9707e200ec90f020af62c2 100644
--- a/include/aidge/operator/ArgMax.hpp
+++ b/include/aidge/operator/ArgMax.hpp
@@ -25,29 +25,35 @@
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
+#define LIST_ARGMAX_ATTR(X)                       \
+    X(Axis, "axis", std::int32_t),                \
+    X(KeepDims, "keep_dims", bool),               \
+    X(SelectLastIndex, "select_last_index", bool) \
 
+namespace Aidge {
+/**
+ * @enum ArgMaxAttr
+ * @brief Attributes for the ArgMax operation.
+ *
+ * - Axis: Specifies the dimension along which the ArgMax operation is performed.
+ * - KeepDims: Indicates whether reduced dimensions should be kept or removed.
+ * - SelectLastIndex: Determines whether to select the first or last index in case of ties.
+ */
 enum class ArgMaxAttr {
-    /**
-     * @brief Specifies the dimension along which the ArgMax operation is performed.
-     */
-    Axis,
-    /**
-     * Indicates whether reduced dimensions should be kept or removed.
-     */
-    KeepDims,
-    /**
-     * Determines whether to select the first or last index in case of ties.
-     */
-    SelectLastIndex
+    GENERATE_LIST_ATTR_ENUM(LIST_ARGMAX_ATTR)
 };
 } // namespace Aidge
-/**
- * @brief Provides string representations for the ArgMaxAttr enumeration.
- */
+
 namespace {
-    template <>
-    const char *const EnumStrings<Aidge::ArgMaxAttr>::data[] = {"axis", "keep_dims", "select_last_index"};
+template <>
+struct EnumStrings<Aidge::ArgMaxAttr> {
+    static const char* const data[];
+};
+/// @brief Provides string representations for the ArgMaxAttr enumeration.
+constexpr const char* const EnumStrings<Aidge::ArgMaxAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_ARGMAX_ATTR)
+};
+
 }
 namespace Aidge {
 /**
@@ -85,9 +91,8 @@ public:
 
 private:
     using Attributes_ = StaticAttributes<ArgMaxAttr,
-                                        std::int32_t,
-                                        bool,
-                                        bool>;
+                            GENERATE_LIST_ATTR_TYPE(LIST_ARGMAX_ATTR)
+                                        >;
     template <ArgMaxAttr e>
     using attr = typename Attributes_::template attr<e>;
     /// Pointer to the attribute storage.
@@ -190,7 +195,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::ArgMaxAttr>::data;
 	}
 };
@@ -214,6 +219,6 @@ std::shared_ptr<Node> ArgMax(std::int32_t axis = 0,
 
 }  // namespace Aidge
 
-
+#undef LIST_ARGMAX_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_ARGMAX_H_ */
diff --git a/include/aidge/operator/AvgPooling.hpp b/include/aidge/operator/AvgPooling.hpp
index 3c7d943a3ef888c410679e6ae4fe22c65d66d144..505a0639851245e4f66a7ccf4456d5a30dd55da3 100644
--- a/include/aidge/operator/AvgPooling.hpp
+++ b/include/aidge/operator/AvgPooling.hpp
@@ -23,42 +23,45 @@
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
+#define LIST_AVGPOOLING_ATTR(X)                     \
+    X(KernelDims, "kernel_dims", sizeArr_t<DIM>),   \
+    X(StrideDims, "stride_dims", sizeArr_t<DIM>),   \
+    X(Dilations,  "dilations",   sizeArr_t<DIM>),   \
+    X(CeilMode,   "ceil_mode",   bool)
+
 namespace Aidge {
+
 /**
- * @brief Attributes specific to the AvgPooling operation.
+ * @enum Attr
+ * @brief Attributes defining the configuration of a MaxPooling Operator.
+ *
+ * - **KernelDims**: Kernel dimensions specifying the size of the pooling window for each spatial dimension.
+ *   Must be an array of positive integers. Common examples include [2,2] or [3,3].
+ * - **StrideDims**: Stride dimensions for sliding the pooling window across the input.
+ *   The stride specifies how much the window moves after each operation.
+ *   Must be an array of positive integers. For example, [1,1] or [2,2].
+ * - **Dilations**: Dilation along each spatial axis. Default value is 1 for all axes.
+ *   Must be an array of positive integers. For example, [1,1].
+ * - **CeilMode**: Flag indicating whether to use ceil or floor when calculating output size.
+ *   - `true`: Use `ceil` for output size calculation.
+ *   - `false`: Use `floor` for output size calculation.
  */
 enum class AvgPoolingAttr {
-    /**
-     * @brief Kernel dimensions for the pooling operation.
-     * Specifies the size of the pooling window along each spatial dimension.
-     */
-    KernelDims,
-    /**
-     * @brief Stride dimensions for sliding the pooling window.
-     * Specifies the step size of the sliding window along each spatial dimension.
-     */
-    StrideDims,
-    /**
-     * @brief Dilation along each spatial axis. Default value is 1.
-     */
-    Dilations,
-    /**
-     * @brief Flag indicating whether to use ceil or floor when calculating output size.
-     * - `true`: Use `ceil` for output size calculation.
-     * - `false`: Use `floor` for output size calculation.
-     */
-    CeilMode
+    GENERATE_LIST_ATTR_ENUM(LIST_AVGPOOLING_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-    /**
-     * @brief String representation of the AvgPooling attributes.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::AvgPoolingAttr>::data[] = {
-        "kernel_dims", "stride_dims", "dilations", "ceil_mode"
-    };
+template <>
+struct EnumStrings<Aidge::AvgPoolingAttr> {
+    static const char* const data[];
+};
+/// @brief String representation of the AvgPooling attributes.
+constexpr const char* const EnumStrings<Aidge::AvgPoolingAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_AVGPOOLING_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief Class representing an Average Pooling operation.
@@ -104,10 +107,8 @@ private:
      * @brief Static attributes representing kernel and stride dimensions.
      */
     using Attributes_ = StaticAttributes<AvgPoolingAttr,
-                                             std::array<DimSize_t, DIM>,
-                                             std::array<DimSize_t, DIM>,
-                                             std::array<DimSize_t, DIM>,
-                                             bool>;
+                            GENERATE_LIST_ATTR_TYPE(LIST_AVGPOOLING_ATTR)
+                                             >;
     template <AvgPoolingAttr e>
     using attr = typename Attributes_::template attr<e>;
 
@@ -238,7 +239,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::AvgPoolingAttr>::data;
 	}
 };
@@ -290,4 +291,6 @@ extern template class Aidge::AvgPooling_Op<2>;
 extern template class Aidge::AvgPooling_Op<3>;
 extern template class Aidge::AvgPooling_Op<4>;
 
+#undef LIST_AVGPOOLING_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_AVGPOOLING_H_ */
diff --git a/include/aidge/operator/BatchNorm.hpp b/include/aidge/operator/BatchNorm.hpp
index 3521c9b16dcbbf73b0c3c4aea9d93047dc0a2f61..81a679502c3ba74747fc7d7612293d4af46acc4f 100644
--- a/include/aidge/operator/BatchNorm.hpp
+++ b/include/aidge/operator/BatchNorm.hpp
@@ -22,39 +22,39 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
+#define LIST_BATCHNORM_ATTR(X)              \
+    X(Epsilon, "epsilon", float),           \
+    X(Momentum, "momentum", float),         \
+    X(TrainingMode,  "training_mode", bool)
 
+namespace Aidge {
+/**
+ * @enum BatchNormAttr
+ * @brief Attributes for the Batch Normalization operation.
+ *
+ * - Epsilon: A small value added to the denominator to ensure numerical stability and avoid division by zero.
+ * - Momentum: Controls the weighting of past running averages in batch normalization statistics.
+ *   - `0.0`: Full reliance on current batch statistics.
+ *   - `1.0`: Complete reliance on the previous moving average.
+ * - TrainingMode: Determines whether the operator is in training mode.
+ *   - `true`: Uses the current batch statistics for normalization.
+ *   - `false`: Uses moving average statistics accumulated during training.
+ */
 enum class BatchNormAttr {
-  /**
-   * @brief Epsilon value to avoid division by zero during normalization.
-   *
-   * A small value added to the denominator during normalization to ensure numerical stability.
-   * Commonly used in batch normalization to avoid very small variance values.
-   */
-  Epsilon,
-
-  /**
-   * @brief Momentum factor for the moving average of batch statistics.
-   *
-   * Controls the weighting of past running averages in the batch normalization statistics.
-   * - `0.0`: Full reliance on current batch statistics.
-   * - `1.0`: Complete reliance on the previous moving average.
-   */
-  Momentum,
-
-  /**
-   * @brief Flag indicating whether the operator is in training mode.
-   *
-   * - `true`: Uses the current batch statistics for normalization.
-   * - `false`: Uses moving average statistics accumulated during training.
-   */
-  TrainingMode
+    GENERATE_LIST_ATTR_ENUM(LIST_BATCHNORM_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-    template <>
-    const char *const EnumStrings<Aidge::BatchNormAttr>::data[] = { "epsilon", "momentum", "training_mode" };
+template <>
+struct EnumStrings<Aidge::BatchNormAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::BatchNormAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_BATCHNORM_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @class BatchNorm_Op
@@ -83,8 +83,9 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<BatchNormAttr, float, float, bool>;
-
+    using Attributes_ = StaticAttributes<BatchNormAttr,
+                            GENERATE_LIST_ATTR_TYPE(LIST_BATCHNORM_ATTR)
+                        >;
     template <BatchNormAttr e>
     using attr = typename Attributes_::template attr<e>;
 
@@ -162,7 +163,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::BatchNormAttr>::data;
 	}
 };
@@ -183,4 +184,6 @@ extern template std::shared_ptr<Aidge::Node> Aidge::BatchNorm<2>(const DimSize_t
 extern template std::shared_ptr<Aidge::Node> Aidge::BatchNorm<3>(const DimSize_t, const float, const float, const bool, const std::string&);
 extern template std::shared_ptr<Aidge::Node> Aidge::BatchNorm<4>(const DimSize_t, const float, const float, const bool, const std::string&);
 
+#undef LIST_BATCHNORM_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_BATCHNORM_H_ */
diff --git a/include/aidge/operator/BitShift.hpp b/include/aidge/operator/BitShift.hpp
index 3e9f8c3f22728afc4fae7abf5f60adc13c89ac76..c54d6a99fc2f945a02396af446d356004e94efc1 100644
--- a/include/aidge/operator/BitShift.hpp
+++ b/include/aidge/operator/BitShift.hpp
@@ -23,23 +23,25 @@
 #include "aidge/utils/Types.h"
 #include "aidge/utils/StaticAttributes.hpp"
 
-namespace Aidge {
-
+#define LIST_BITSHIFT_ATTR(X) X(BitShiftdirection, "bit_shift_direction", BitShiftDirection)
 
+namespace Aidge {
 enum class BitShiftAttr {
-    /**
-     *
-     */
-    BitShiftdirection
+    GENERATE_LIST_ATTR_ENUM(LIST_BITSHIFT_ATTR)
 };
-}
+}  // namespace Aidge
+
 namespace {
-    /**
-     * @brief Specialization of `EnumStrings` for `BitShiftAttr`.
-     */
-    template <>
-    const char* const EnumStrings<Aidge::BitShiftAttr>::data[] = {"bit_shift_direction"};
+/// @brief Specialization of `EnumStrings` for `BitShiftAttr`.
+template <>
+struct EnumStrings<Aidge::BitShiftAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::BitShiftAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_BITSHIFT_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @class BitShift_Op
@@ -71,7 +73,9 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<BitShiftAttr, BitShiftDirection>;
+    using Attributes_ = StaticAttributes<BitShiftAttr,
+                            GENERATE_LIST_ATTR_TYPE(LIST_BITSHIFT_ATTR)
+                        >;
 
     template <BitShiftAttr e>
     using attr = typename Attributes_::template attr<e>;
@@ -160,7 +164,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::BitShiftAttr>::data;
 	}
 };
@@ -177,6 +181,6 @@ inline std::shared_ptr<Node> BitShift(const BitShift_Op::BitShiftDirection direc
 
 } // namespace Aidge
 
-
+#undef LIST_BITSHIFT_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_BITSHIFT_H_ */
diff --git a/include/aidge/operator/Cast.hpp b/include/aidge/operator/Cast.hpp
index b2ffbb553ce44f66f371a65f35340193bf04dab4..f003e30c30ce8bfa7a33d65b75bb83fc8ec17d93 100644
--- a/include/aidge/operator/Cast.hpp
+++ b/include/aidge/operator/Cast.hpp
@@ -30,20 +30,28 @@ public:
     void forward() override;
 };
 
+#define LIST_CAST_ATTR(X)  \
+    X(TargetType, "target_type", DataType)
+
 /**
  * @enum CastAttr
  * @brief Enum class defining the attributes for the Cast operator.
+ *
+ * - TargetType: Specifies the target data type for the cast operation.
  */
 enum class CastAttr {
-    /**
-     * @brief Target data type for the cast operation.
-     */
-    TargetType
+    GENERATE_LIST_ATTR_ENUM(LIST_CAST_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-    template <>
-    const char* const EnumStrings<Aidge::CastAttr>::data[] = { "target_type" };
+template <>
+struct EnumStrings<Aidge::CastAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::CastAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_CAST_ATTR)
+};
 }
 namespace Aidge {
 /**
@@ -67,7 +75,9 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<CastAttr, DataType>;
+    using Attributes_ = StaticAttributes<CastAttr,
+        GENERATE_LIST_ATTR_TYPE(LIST_CAST_ATTR)
+    >;
 
     template <CastAttr e>
     using attr = typename Attributes_::template attr<e>;
@@ -147,7 +157,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::CastAttr>::data;
 	}
 };
@@ -162,4 +172,6 @@ std::shared_ptr<Node> Cast(const DataType targetType, const std::string& name =
 
 }  // namespace Aidge
 
+#undef LIST_CAST_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_CAST_H_ */
diff --git a/include/aidge/operator/Clip.hpp b/include/aidge/operator/Clip.hpp
index 51ecb6eb36591c2e22ea47ba529b87d125c92a65..4d5d2a93c3434c53b48d718b0b908edc3f402dcb 100644
--- a/include/aidge/operator/Clip.hpp
+++ b/include/aidge/operator/Clip.hpp
@@ -23,23 +23,32 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
 
+#define LIST_CLIP_ATTR(X)  \
+    X(Min, "min", float),  \
+    X(Max, "max", float)
+
+namespace Aidge {
 /**
  * @enum ClipAttr
  * @brief Enum class defining the attributes for the Clip operator.
+ *
+ * - Min: Minimum value for clipping.
+ * - Max: Maximum value for clipping.
  */
 enum class ClipAttr {
-    Min,  /**< Minimum value for clipping. */
-    Max   /**< Maximum value for clipping. */
+    GENERATE_LIST_ATTR_ENUM(LIST_CLIP_ATTR)
 };
-}
+}  // namespace Aidge
+
 namespace {
-    /**
-     * @brief Specialization of EnumStrings for ClipAttr.
-     */
-    template <>
-    const char* const EnumStrings<Aidge::ClipAttr>::data[] = { "min", "max" };
+template <>
+struct EnumStrings<Aidge::ClipAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ClipAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_CLIP_ATTR)
+};
 }
 
 namespace Aidge {
@@ -69,7 +78,9 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<ClipAttr, float, float>;
+    using Attributes_ = StaticAttributes<ClipAttr,
+        GENERATE_LIST_ATTR_TYPE(LIST_CLIP_ATTR)
+    >;
 
     template <ClipAttr e>
     using attr = typename Attributes_::template attr<e>;
@@ -162,7 +173,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::ClipAttr>::data;
 	}
 };
@@ -182,4 +193,6 @@ std::shared_ptr<Aidge::Node> Clip(
 
 } // namespace Aidge
 
+#undef LIST_CLIP_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_CLIP_H_ */
diff --git a/include/aidge/operator/Concat.hpp b/include/aidge/operator/Concat.hpp
index 1f8a357a830ef3bf3d945ea488425128ea99d3ed..3e5efb5f9bbdecb248d6fd9ab647469955aa0214 100644
--- a/include/aidge/operator/Concat.hpp
+++ b/include/aidge/operator/Concat.hpp
@@ -49,25 +49,34 @@ public:
      */
     void forward() override;
 };
+}  // namespace Aidge
 
+
+#define LIST_CONCAT_ATTR(X)  \
+    X(Axis, "axis", std::int32_t)
+
+namespace Aidge {
+/**
+ * @enum ConcatAttr
+ * @brief Attributes for the Concat operation.
+ *
+ * - Axis: index of dimension along which the input tensors are concatenated.
+ */
 enum class ConcatAttr {
-    /**
-     * @brief Axis along which to concat the input tensor.
-     *
-     * The specified axis determines the direction of concatenating.
-     */
-    Axis
+    GENERATE_LIST_ATTR_ENUM(LIST_CONCAT_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-    /**
-     * @brief Specialization of EnumStrings for ConcatAttr.
-     */
-    template <>
-    const char* const EnumStrings<Aidge::ConcatAttr>::data[] = {
-        "axis"
-    };
+template <>
+struct EnumStrings<Aidge::ConcatAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ConcatAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_CONCAT_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @class Concat_Op
@@ -99,7 +108,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<ConcatAttr, std::int32_t>;
+    using Attributes_ = StaticAttributes<ConcatAttr, GENERATE_LIST_ATTR_TYPE(LIST_CONCAT_ATTR)>;
 
     template <ConcatAttr e>
     using attr = typename Attributes_::template attr<e>;
@@ -184,7 +193,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::ConcatAttr>::data;
 	}
 };
@@ -200,4 +209,6 @@ std::shared_ptr<Node> Concat(const IOIndex_t nbIn, const std::int32_t axis = 0,
 
 } // namespace Aidge
 
+#undef LIST_CONCAT_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_CONCAT_H_ */
diff --git a/include/aidge/operator/ConstantOfShape.hpp b/include/aidge/operator/ConstantOfShape.hpp
index e78fba12ec89be456da0aca25c9bb15e170bdede..886df95a8f2478f84eae1f5548d558b2e5c4649b 100644
--- a/include/aidge/operator/ConstantOfShape.hpp
+++ b/include/aidge/operator/ConstantOfShape.hpp
@@ -12,41 +12,45 @@
 #ifndef AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_
 #define AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_
 
-#include <cstdint>
-#include <cstdlib>
-#include <functional>
-#include <limits>
 #include <memory>
+#include <functional>
+#include <set>
 #include <string>
-#include <vector>
 
-#include "aidge/data/Data.hpp"
-#include "aidge/graph/Node.hpp"
-#include "aidge/operator/Operator.hpp"
+#include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/data/Tensor.hpp"
+#include "aidge/graph/Node.hpp"
 #include "aidge/operator/OperatorTensor.hpp"
-#include "aidge/utils/ErrorHandling.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/StaticAttributes.hpp"
-#include "aidge/utils/Types.h"
 
-namespace Aidge {
 
+#define LIST_CONSTANTOFSHAPE_ATTR(X)  \
+    X(Value, "value", Tensor)
+
+namespace Aidge {
+/**
+ * @enum ConstantOfShapeAttr
+ * @brief Attributes for the ConstantOfShape operation.
+ *
+ * - Value: A scalar tensor that holds a fixed datatype value to fill the output tensor.
+ */
 enum class ConstantOfShapeAttr {
-  /**
-   * @brief value to fill the output tensor with.
-   * Its a scalar tensor holding a value with a fixed datatype
-   */
-  Value,
+    GENERATE_LIST_ATTR_ENUM(LIST_CONSTANTOFSHAPE_ATTR)
 };
-} // namespace Aidge
-namespace {
-  template <>
-  const char *const EnumStrings<Aidge::ConstantOfShapeAttr>::data[] = {"value"};
- } //namespace
+}  // namespace Aidge
 
-  namespace Aidge {
+namespace {
+template <>
+struct EnumStrings<Aidge::ConstantOfShapeAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ConstantOfShapeAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_CONSTANTOFSHAPE_ATTR)
+};
+}
 
+namespace Aidge {
 /**
  * @brief This operator's purpose is to generate a tensor of shape given via
  * input and filled with a given value set via attribute.
@@ -62,7 +66,7 @@ public:
   static const std::string Type;
 
 private:
-  using Attributes_ = StaticAttributes<ConstantOfShapeAttr, Tensor>;
+  using Attributes_ = StaticAttributes<ConstantOfShapeAttr, GENERATE_LIST_ATTR_TYPE(LIST_CONSTANTOFSHAPE_ATTR)>;
   template <ConstantOfShapeAttr e>
   using attr = typename Attributes_::template attr<e>;
   const std::shared_ptr<Attributes_> mAttributes;
@@ -119,18 +123,20 @@ public:
     return mAttributes->template getAttr<ConstantOfShapeAttr::Value>();
   }
 
-  static const std::vector<std::string> getInputsName() { return {"input"}; }
-  static const std::vector<std::string> getOutputsName() {
-    return {"constant_of_shape"};
-  }
+    static const std::vector<std::string> getInputsName() noexcept {
+        return {"input"};
+    }
+    static const std::vector<std::string> getOutputsName() noexcept {
+        return {"constant_of_shape"};
+    }
 
-	/**
-	 * @brief Retrieves the names of the attributes for the operator.
-	 * @return A vector containing the attributes name.
-	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::ConstantOfShapeAttr>::data;
-	}
+    /**
+     * @brief Retrieves the names of the attributes for the operator.
+     * @return A vector containing the attributes name.
+     */
+    static constexpr const char* const* attributesName() noexcept {
+        return EnumStrings<Aidge::ConstantOfShapeAttr>::data;
+    }
 };
 
 // helper with C-style array instead of std::array for kernel_dims to allow
@@ -142,5 +148,6 @@ inline std::shared_ptr<Node> ConstantOfShape(const Tensor value = Tensor(0.f),
 }
 } // namespace Aidge
 
-#endif // AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_
+#undef LIST_CONSTANTOFSHAPE_ATTR
 
+#endif // AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_
diff --git a/include/aidge/operator/Conv.hpp b/include/aidge/operator/Conv.hpp
index f9c9109282cb90dadfa9b26d6f830faf9fdecd7c..283d0136e50e7c051061acb5274e98368b3d26a2 100644
--- a/include/aidge/operator/Conv.hpp
+++ b/include/aidge/operator/Conv.hpp
@@ -13,43 +13,37 @@
 #define AIDGE_CORE_OPERATOR_CONV_H_
 
 #include <array>
-#include <cmath>    // std::floor
 #include <cstddef>  // std::size_t
 #include <string>
 #include <utility>  // std::pair
 #include <vector>
 
-#include "aidge/data/Tensor.hpp"
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/OperatorTensor.hpp"
-#include "aidge/operator/Producer.hpp"
 #include "aidge/utils/ArrayHelpers.hpp"
-#include "aidge/utils/ErrorHandling.hpp"
 #include "aidge/utils/Registrar.hpp" // SET_IMPL_MACRO
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
 
+#define LIST_CONV_ATTR(X)                            \
+    X(KernelDims, "kernel_dims", sizeArr_t<DIM>),    \
+    X(StrideDims, "stride_dims", sizeArr_t<DIM>),    \
+    X(DilationDims, "dilation_dims", sizeArr_t<DIM>)
+
+namespace Aidge {
 /**
- * @enum ConvAttr
+ * @enum Attr
  * @brief Attributes used for the Convolution operation.
+ *
+ * - StrideDims: The stride dimensions.
+ * - DilationDims: The dilation dimensions.
+ * - KernelDims: The kernel dimensions.
  */
 enum class ConvAttr {
-    StrideDims,     // The stride dimensions
-    DilationDims,   // The dilation dimensions
-    KernelDims      // The kernel dimensions
+    GENERATE_LIST_ATTR_ENUM(LIST_CONV_ATTR)
 };
-} // namespace Aidge
-namespace {
-    template <>
-    const char *const EnumStrings<Aidge::ConvAttr>::data[] = {
-        "stride_dims",
-        "dilation_dims",
-        "kernel_dims"
-    };
-}
-namespace Aidge {
+
 /**
  * @class Conv_Op
  * @brief Convolution operator for performing a multi-dimensional convolution.
@@ -85,15 +79,13 @@ class Conv_Op : public OperatorTensor,
 public:
     static const std::string Type;
 
-private:
-    using Attributes_ = StaticAttributes<ConvAttr,
-                                        std::array<DimSize_t, DIM>,
-                                        std::array<DimSize_t, DIM>,
-                                        std::array<DimSize_t, DIM>>;
+    // Use the external enum so that Aidge::Conv_Op<DIM>::Attr is valid.
+    using Attr = ConvAttr;
 
-    template <ConvAttr e>
+private:
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_CONV_ATTR)>;
+    template <Attr e>
     using attr = typename Attributes_::template attr<e>;
-
     const std::shared_ptr<Attributes_> mAttributes;
 
 public:
@@ -110,9 +102,9 @@ public:
                       const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1))
         : OperatorTensor(Type, {InputCategory::Data, InputCategory::Param, InputCategory::OptionalParam}, 1),
           mAttributes(std::make_shared<Attributes_>(
-            attr<ConvAttr::StrideDims>(strideDims),
-            attr<ConvAttr::DilationDims>(dilationDims),
-            attr<ConvAttr::KernelDims>(kernelDims)))
+            attr<Attr::StrideDims>(strideDims),
+            attr<Attr::DilationDims>(dilationDims),
+            attr<Attr::KernelDims>(kernelDims)))
     {}
 
     /**
@@ -168,30 +160,14 @@ public:
      * @return The number of input channels.
      * @throws std::runtime_error If the operator has no associated weight tensor.
      */
-    DimSize_t inChannels() const {
-        if (!getInput(1)) {
-            AIDGE_THROW_OR_ABORT(std::runtime_error, "Convolution operator has no weight Tensor associated so no specific number of input channel imposed.");
-        }
-        
-        // check format
-        if(getInput(1)->dataFormat()==Aidge::DataFormat::NHWC) 
-            return getInput(1)->template dims<DIM+2>()[DIM+1];
-        // default format is NCHW
-        return getInput(1)->template dims<DIM+2>()[1];
-    }
+    DimSize_t inChannels() const;
 
     /**
      * @brief Get the number of output channels.
      * @return The number of output channels.
      * @throws std::runtime_error If the operator has no associated weight tensor.
      */
-    DimSize_t outChannels() const {
-        if (!getInput(1)) {
-            AIDGE_THROW_OR_ABORT(std::runtime_error, "Convolution operator has no weight Tensor associated so no specific number of output channel imposed.");
-        }
-        // first weight dimension for both NCHW (Cout,Cin,H,W) and NHWC (Cout,H,W,Cin) data format
-        return getInput(1)->template dims<DIM+2>()[0];
-    }
+    DimSize_t outChannels() const;
 
     /**
      * @brief Get the attributes of the operator.
@@ -203,19 +179,19 @@ public:
      * @brief Get the stride dimensions.
      * @return The stride dimensions as a reference.
      */
-    inline std::array<DimSize_t, DIM>& strideDims() const { return mAttributes->template getAttr<ConvAttr::StrideDims>(); }
+    inline std::array<DimSize_t, DIM>& strideDims() const { return mAttributes->template getAttr<Attr::StrideDims>(); }
 
     /**
      * @brief Get the dilation dimensions.
      * @return The dilation dimensions as a reference.
      */
-    inline std::array<DimSize_t, DIM>& dilationDims() const { return mAttributes->template getAttr<ConvAttr::DilationDims>(); }
+    inline std::array<DimSize_t, DIM>& dilationDims() const { return mAttributes->template getAttr<Attr::DilationDims>(); }
 
     /**
      * @brief Get the kernel dimensions.
      * @return The kernel dimensions as a reference.
      */
-    inline std::array<DimSize_t, DIM>& kernelDims() const { return mAttributes->template getAttr<ConvAttr::KernelDims>(); }
+    inline std::array<DimSize_t, DIM>& kernelDims() const { return mAttributes->template getAttr<Attr::KernelDims>(); }
 
     static const std::vector<std::string> getInputsName(){
         return {"data_input", "weight", "bias"};
@@ -229,9 +205,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::ConvAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 };
 
 /**
@@ -266,22 +240,35 @@ std::shared_ptr<Node> Conv(DimSize_t inChannels,
  * based on the kernel dimensions provided.
  */
 template <DimSize_t DIM>
-inline std::shared_ptr<Node> Conv(
+std::shared_ptr<Node> Conv(
     DimSize_t inChannels,
     DimSize_t outChannels,
     DimSize_t const (&kernelDims)[DIM],
     const std::string& name = "",
     const std::array<DimSize_t, DIM> &strideDims = create_array<DimSize_t,DIM>(1),
     const std::array<DimSize_t, DIM> &dilationDims = create_array<DimSize_t,DIM>(1),
-    bool noBias = false) {
-    static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Conv, not supported");
-    return Conv(inChannels, outChannels, to_array(kernelDims), name, strideDims, dilationDims, noBias);
-}
+    bool noBias = false);
 
 }  // namespace Aidge
 
+namespace {
+template <>
+struct EnumStrings<Aidge::ConvAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ConvAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_CONV_ATTR)
+};
+}
+
+template <Aidge::DimIdx_t DIM>
+constexpr const char* const* Aidge::Conv_Op<DIM>::attributesName() {
+    return EnumStrings<Aidge::Conv_Op<DIM>::Attr>::data;
+}
+
 extern template class Aidge::Conv_Op<1>;
 extern template class Aidge::Conv_Op<2>;
 
+#undef LIST_CONV_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_CONV_H_ */
diff --git a/include/aidge/operator/ConvDepthWise.hpp b/include/aidge/operator/ConvDepthWise.hpp
index b307d67a61cabd416bb96db8558fb6960cd65cc4..341b6f76647059e94613feb0b87dfb3a0187d875 100644
--- a/include/aidge/operator/ConvDepthWise.hpp
+++ b/include/aidge/operator/ConvDepthWise.hpp
@@ -28,21 +28,35 @@
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
+#define LIST_CONVDEPTHWISE_ATTR(X)                   \
+    X(KernelDims, "kernel_dims", sizeArr_t<DIM>),    \
+    X(StrideDims, "stride_dims", sizeArr_t<DIM>),    \
+    X(DilationDims, "dilation_dims", sizeArr_t<DIM>)
+
 namespace Aidge {
+/**
+ * @enum ConvDepthWiseAttr
+ * @brief Attributes used for the Convolution operation.
+ *
+ * - StrideDims: The stride dimensions.
+ * - DilationDims: The dilation dimensions.
+ * - KernelDims: The kernel dimensions.
+ */
 enum class ConvDepthWiseAttr {
-    StrideDims,   // The stride dimensions for the convolution.
-    DilationDims, // The dilation dimensions for the convolution.
-    KernelDims    // The kernel dimensions for the convolution.
+    GENERATE_LIST_ATTR_ENUM(LIST_CONVDEPTHWISE_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-    template <>
-    const char *const EnumStrings<Aidge::ConvDepthWiseAttr>::data[] = {
-        "stride_dims",
-        "dilation_dims",
-        "kernel_dims"
-    };
+template <>
+struct EnumStrings<Aidge::ConvDepthWiseAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ConvDepthWiseAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_CONVDEPTHWISE_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @class ConvDepthWise_Op
@@ -72,9 +86,8 @@ public:
 
 private:
     using Attributes_ = StaticAttributes<ConvDepthWiseAttr,
-                                        std::array<DimSize_t, DIM>,
-                                        std::array<DimSize_t, DIM>,
-                                        std::array<DimSize_t, DIM>>;
+                            GENERATE_LIST_ATTR_TYPE(LIST_CONVDEPTHWISE_ATTR)
+                        >;
 
     template <ConvDepthWiseAttr e>
     using attr = typename Attributes_::template attr<e>;
@@ -203,7 +216,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::ConvDepthWiseAttr>::data;
 	}
 };
@@ -254,4 +267,6 @@ inline std::shared_ptr<Node> ConvDepthWise(
 extern template class Aidge::ConvDepthWise_Op<1>;
 extern template class Aidge::ConvDepthWise_Op<2>;
 
+#undef LIST_CONVDEPTHWISE_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_CONVDEPTHWISE_H_ */
diff --git a/include/aidge/operator/DepthToSpace.hpp b/include/aidge/operator/DepthToSpace.hpp
index c99f7bbb7d882300b7f2f4278dda832189064ad5..7bf6ffdf3ad63986049558374afff642a71fc549 100644
--- a/include/aidge/operator/DepthToSpace.hpp
+++ b/include/aidge/operator/DepthToSpace.hpp
@@ -42,20 +42,35 @@ public:
      */
     void forward() override;
 };
+}  // namespace Aidge
+
+#define LIST_DEPTHTOSPACE_ATTR(X)               \
+    X(BlockSize, "block_size", std::uint32_t),    \
+    X(Mode, "mode", Aidge::DepthToSpace_Op::Mode)
 
+namespace Aidge {
 /**
  * @enum DepthToSpaceAttr
  * @brief Attributes for the DepthToSpace operation.
+ *
+ * - BlockSize: The block size for rearranging depth to spatial dimensions.
+ * - Mode: The mode for depth-to-space transformation.
  */
 enum class DepthToSpaceAttr {
-    BlockSize, /**< The block size for rearranging depth to spatial dimensions. */
-    Mode       /**< The mode for depth-to-space transformation. */
+    GENERATE_LIST_ATTR_ENUM(LIST_DEPTHTOSPACE_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-    template <>
-    const char *const EnumStrings<Aidge::DepthToSpaceAttr>::data[] = { "block_size", "mode" };
+template <>
+struct EnumStrings<Aidge::DepthToSpaceAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::DepthToSpaceAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_DEPTHTOSPACE_ATTR)
+};
 }
+
 namespace Aidge{
 /**
  * @class DepthToSpace_Op
@@ -92,7 +107,7 @@ public:
     enum class Mode { DCR, CRD };
 
 private:
-    using Attributes_ = StaticAttributes<DepthToSpaceAttr, std::uint32_t, Mode>;
+    using Attributes_ = StaticAttributes<DepthToSpaceAttr, GENERATE_LIST_ATTR_TYPE(LIST_DEPTHTOSPACE_ATTR)>;
     template <DepthToSpaceAttr e>
     using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
@@ -174,7 +189,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::DepthToSpaceAttr>::data;
 	}
 };
@@ -192,5 +207,6 @@ std::shared_ptr<Node> DepthToSpace(const std::uint32_t blockSize,
 
 }  // namespace Aidge
 
+#undef LIST_DEPTHTOSPACE_ATTR
 
 #endif //AIDGE_CORE_OPERATOR_DEPTHTOSPACE_H_
diff --git a/include/aidge/operator/Flatten.hpp b/include/aidge/operator/Flatten.hpp
index b61fc6912dd0e9f61dd2506370c591aae8c3a107..0ccc54eb770421dd726658dfbd4e44ee78f28cfb 100644
--- a/include/aidge/operator/Flatten.hpp
+++ b/include/aidge/operator/Flatten.hpp
@@ -43,22 +43,33 @@ public:
      */
     void forward() override;
 };
+}  // namespace Aidge
+
+#define LIST_FLATTEN_ATTR(X)  \
+    X(Axis, "axis", std::int64_t)
 
+namespace Aidge {
 /**
  * @enum FlattenAttr
  * @brief Defines attributes for the Flatten operator.
+ *
+ * - Axis: dimension index at which to flatten the input tensor.
  */
 enum class FlattenAttr {
-    /**
-     * @brief The axis at which to flatten the input tensor.
-     */
-    Axis
+    GENERATE_LIST_ATTR_ENUM(LIST_FLATTEN_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-    template <>
-    const char *const EnumStrings<Aidge::FlattenAttr>::data[] = { "axis" };
+template <>
+struct EnumStrings<Aidge::FlattenAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::FlattenAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_FLATTEN_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief Description the Flatten operation to reshape a tensor into a 2D matrix.
@@ -85,7 +96,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<FlattenAttr, std::int64_t>;
+    using Attributes_ = StaticAttributes<FlattenAttr, GENERATE_LIST_ATTR_TYPE(LIST_FLATTEN_ATTR)>;
     template <FlattenAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -165,7 +176,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::FlattenAttr>::data;
 	}
 };
@@ -184,5 +195,6 @@ std::shared_ptr<Node> Flatten(std::int64_t axis = 1,
                             const std::string &name = "");
 }  // namespace Aidge
 
+#undef LIST_FLATTEN_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_FLATTEN_H_ */
diff --git a/include/aidge/operator/Fold.hpp b/include/aidge/operator/Fold.hpp
index 2f9974e8ed3b1723734a2483616feceace5bec33..9b71057fb20327c7c37d3ac9aa49d021e7c244cc 100644
--- a/include/aidge/operator/Fold.hpp
+++ b/include/aidge/operator/Fold.hpp
@@ -29,51 +29,37 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
+#define LIST_FOLD_ATTR(X)  \
+    X(OutputDims, "output_dims", sizeArr_t<DIM>),  \
+    X(StrideDims, "stride_dims", sizeArr_t<DIM>),  \
+    X(DilationDims, "dilation_dims", sizeArr_t<DIM>),  \
+    X(KernelDims, "kernel_dims", sizeArr_t<DIM>)
 
+namespace Aidge {
 /**
  * @enum FoldAttr
  * @brief Enumeration for the attributes of the Fold operation.
+ *
+ * - OutputDims: Specifies the shape of the output tensor after applying the fold operation.
+ * - StrideDims: Step sizes in each dimension during the fold operation.
+ * - DilationDims: Spacing between elements in the kernel during the fold.
+ * - KernelDims: Size of the kernel or filter applied during the fold.
  */
 enum class FoldAttr {
-    /**
-     * @brief Output dimensions of the fold operation.
-     *
-     * Specifies the shape of the output tensor after applying the fold operation.
-     */
-    OutputDims,
-
-    /**
-     * @brief Stride dimensions used during the fold operation.
-     *
-     * Strides are the step sizes in each dimension during the fold operation.
-     */
-    StrideDims,
-
-    /**
-     * @brief Dilation dimensions for the fold operation.
-     *
-     * Dilation is the spacing between elements in the kernel during the fold.
-     */
-    DilationDims,
-
-    /**
-     * @brief Kernel dimensions used for the fold operation.
-     *
-     * Specifies the size of the kernel or filter applied during the fold.
-     */
-    KernelDims
+    GENERATE_LIST_ATTR_ENUM(LIST_FOLD_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-    template <>
-    const char* const EnumStrings<Aidge::FoldAttr>::data[] = {
-        "output_dims",
-        "stride_dims",
-        "dilation_dims",
-        "kernel_dims"
-    };
+template <>
+struct EnumStrings<Aidge::FoldAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::FoldAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_FOLD_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @class Fold_Op
@@ -112,11 +98,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<FoldAttr,
-                                        std::array<DimSize_t, DIM>,
-                                        std::array<DimSize_t, DIM>,
-                                        std::array<DimSize_t, DIM>,
-                                        std::array<DimSize_t, DIM>>;
+    using Attributes_ = StaticAttributes<FoldAttr, GENERATE_LIST_ATTR_TYPE(LIST_FOLD_ATTR)>;
 
     template <FoldAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
@@ -225,7 +207,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::FoldAttr>::data;
 	}
 };
@@ -265,4 +247,6 @@ extern template class Aidge::Fold_Op<2>;
 
 }  // namespace Aidge
 
+#undef LIST_FOLD_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_FOLD_H_ */
diff --git a/include/aidge/operator/Gather.hpp b/include/aidge/operator/Gather.hpp
index 86fc7bc7855473c6f73e3bcc36d46ef9b4956446..60bec8d1c571eb7c5be2d99527d233fa2ac53e82 100644
--- a/include/aidge/operator/Gather.hpp
+++ b/include/aidge/operator/Gather.hpp
@@ -43,29 +43,15 @@ public:
      */
     void forward() override;
 };
+} // namespace Aidge
 
-enum class GatherAttr {
-    /**
-     * @brief Axis along which to gather elements.
-     */
-    Axis,
 
-    /**
-     * @brief Indices specifying which elements to gather.
-     */
-    Indices,
+#define LIST_GATHER_ATTR(X)  \
+    X(Axis, "axis", std::int8_t),  \
+    X(Indices, "indices", std::vector<int64_t>),  \
+    X(GatheredShape, "gathered_shape", std::vector<DimSize_t>)
 
-    /**
-     * @brief Shape of the resulting gathered tensor.
-     */
-    GatheredShape
-};
 
-} // namespace Aidge
-namespace {
-    template <>
-    const char *const EnumStrings<Aidge::GatherAttr>::data[] = {"axis", "indices", "gathered_shape"};
-}
 namespace Aidge {
 /**
  * @brief Description for the Gather operation on an input tensor.
@@ -86,13 +72,21 @@ class Gather_Op : public OperatorTensor,
 public:
     static const std::string Type;
 
-    using Attributes_ = StaticAttributes<GatherAttr,
-                                          std::int8_t,
-                                          std::vector<int64_t>,
-                                          std::vector<DimSize_t>>;
+    /**
+     * @enum Attr
+     * @brief Attributes for the Gather operation.
+     *
+     * - Axis: The axis along which to gather elements.
+     * - Indices: Specifies which elements to gather.
+     * - GatheredShape: The shape of the resulting gathered tensor.
+     */
+    enum class Attr {
+        GENERATE_LIST_ATTR_ENUM(LIST_GATHER_ATTR)
+    };
 
 private:
-    template <GatherAttr e>
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_GATHER_ATTR)>;
+    template <Attr e>
     using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -161,19 +155,19 @@ public:
      * @brief Get the axis along which elements are gathered.
      * @return The axis attribute.
      */
-    inline std::int8_t& axis() const { return mAttributes->getAttr<GatherAttr::Axis>(); }
+    inline std::int8_t& axis() const { return mAttributes->getAttr<Attr::Axis>(); }
 
     /**
      * @brief Get the indices specifying which elements to gather.
      * @return The indices attribute.
      */
-    inline std::vector<int64_t>& indices() const { return mAttributes->getAttr<GatherAttr::Indices>(); }
+    inline std::vector<int64_t>& indices() const { return mAttributes->getAttr<Attr::Indices>(); }
 
     /**
      * @brief Get the shape of the gathered tensor.
      * @return The gathered shape attribute.
      */
-    inline std::vector<DimSize_t>& gatheredShape() const { return mAttributes->getAttr<GatherAttr::GatheredShape>(); }
+    inline std::vector<DimSize_t>& gatheredShape() const { return mAttributes->getAttr<Attr::GatheredShape>(); }
 
     /**
      * @brief Get the input tensor names.
@@ -195,9 +189,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::GatherAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 };
 
 /**
@@ -219,5 +211,20 @@ std::shared_ptr<Node> Gather(std::int8_t axis = 0,
 
 } // namespace Aidge
 
+namespace {
+template <>
+struct EnumStrings<Aidge::Gather_Op::Attr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::Gather_Op::Attr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_GATHER_ATTR)
+};
+}
+
+constexpr const char* const* attributesName() {
+    return EnumStrings<Aidge::Gather_Op::Attr>::data;
+}
+
+#undef LIST_GATHER_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_GATHER_H_ */
diff --git a/include/aidge/operator/GridSample.hpp b/include/aidge/operator/GridSample.hpp
index 06642231152cefe1023688811da0dcdc0bbde859..2388cd0c17339ea50a0ea2f9047c8cdabb08a68e 100644
--- a/include/aidge/operator/GridSample.hpp
+++ b/include/aidge/operator/GridSample.hpp
@@ -23,21 +23,35 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/logger/EnumString.hpp"
 
+#define LIST_GRIDSAMPLE_ATTR(X)                  \
+    X(Mode, "mode", Mode),                       \
+    X(PaddingMode, "padding_mode", PaddingMode), \
+    X(AlignCorners, "align_corners", bool)
+
 namespace Aidge {
+/**
+ * @enum GridSampleAttr
+ * @brief Attributes for the GridSample operation.
+ *
+ * - Mode: Specifies the interpolation mode (e.g., Linear, Nearest, Cubic).
+ * - PaddingMode: Specifies how to handle out-of-boundary grid values.
+ * - AlignCorners: Determines whether grid values are normalized to align with the image corners.
+ */
 enum class GridSampleAttr {
-	Mode,			// Specifies the interpolation mode (e.g., Linear, Nearest, Cubic).
-	PaddingMode,	// Specifies how to handle out-of-boundary grid values.
-	AlignCorners	// Determines whether grid values are normalized to align with the image corners.
+    GENERATE_LIST_ATTR_ENUM(LIST_GRIDSAMPLE_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-	template <>
-	const char* const EnumStrings<Aidge::GridSampleAttr>::data[] = {
-		"mode",
-		"padding_mode",
-		"align_corners"
-	};
+template <>
+struct EnumStrings<Aidge::GridSampleAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::GridSampleAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_GRIDSAMPLE_ATTR)
+};
 }
+
 namespace Aidge {
 
 /**
@@ -88,7 +102,7 @@ public:
 	enum class PaddingMode { Zeros, Border, Reflection };
 
 private:
-	using Attributes_ = StaticAttributes<GridSampleAttr, Mode, PaddingMode, bool>;
+	using Attributes_ = StaticAttributes<GridSampleAttr, GENERATE_LIST_ATTR_TYPE(LIST_GRIDSAMPLE_ATTR)>;
 	template <GridSampleAttr e>
 	using attr = typename Attributes_::template attr<e>;
 	const std::shared_ptr<Attributes_> mAttributes;
@@ -185,7 +199,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::GridSampleAttr>::data;
 	}
 };
@@ -207,4 +221,6 @@ std::shared_ptr<Node> GridSample(
 
 } // namespace Aidge
 
+#undef LIST_GRIDSAMPLE_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_GRIDSAMPLE_H_ */
diff --git a/include/aidge/operator/Heaviside.hpp b/include/aidge/operator/Heaviside.hpp
index 806ed47f3db5f78b5636f7f14876f852ea22b341..49f9059033b2816b594802b1fcfaa4340418f883 100644
--- a/include/aidge/operator/Heaviside.hpp
+++ b/include/aidge/operator/Heaviside.hpp
@@ -24,23 +24,10 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
-enum class HeavisideAttr {
-    /**
-     * @brief The value used in the output tensor when the input is 0.
-     */
-    Value
-};
-} // namespace Aidge
-namespace {
-    /**
-     * @brief Define string representations for Heaviside attributes.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::HeavisideAttr>::data[] = {"value"};
-}
-namespace Aidge {
+#define LIST_HEAVISIDE_ATTR(X)  \
+    X(Value, "value", float)
 
+namespace Aidge {
 /**
  * @class Heaviside_Op
  * @brief Implements the Heaviside step function operation.
@@ -59,18 +46,26 @@ namespace Aidge {
 class Heaviside_Op
     : public OperatorTensor,
       public Registrable<Heaviside_Op, std::string, std::function<std::shared_ptr<OperatorImpl>(const Heaviside_Op &)>> {
+public:
+    static const std::string Type;
 
-private:
-    using Attributes_ = StaticAttributes<HeavisideAttr, float>;
+    /**
+     * @enum Attr
+     * @brief Attributes for the Heaviside operation.
+     *
+     * - Value: The value used in the output tensor when the input is 0.
+     */
+    enum class Attr {
+        GENERATE_LIST_ATTR_ENUM(LIST_HEAVISIDE_ATTR)
+    };
 
-    template <HeavisideAttr e>
+private:
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_HEAVISIDE_ATTR)>;
+    template <Attr e>
     using attr = typename Attributes_::template attr<e>;
-
     const std::shared_ptr<Attributes_> mAttributes;
 
 public:
-    static const std::string Type;
-
     /**
      * @brief Constructor for the Heaviside operator.
      * @param[in] value The value to use in the output tensor when the input is 0.
@@ -123,9 +118,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::HeavisideAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 
     /**
      * @brief Get the attributes of the operator.
@@ -139,7 +132,7 @@ public:
      * @return A reference to the value attribute.
      */
     inline float &value() const {
-        return mAttributes->template getAttr<HeavisideAttr::Value>();
+        return mAttributes->template getAttr<Attr::Value>();
     }
 };
 
@@ -158,5 +151,20 @@ std::shared_ptr<Node> Heaviside(float value, const std::string &name = "");
 
 } // namespace Aidge
 
+namespace {
+template <>
+struct EnumStrings<Aidge::Heaviside_Op::Attr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::Heaviside_Op::Attr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_HEAVISIDE_ATTR)
+};
+}
+
+constexpr const char* const* Aidge::Heaviside_Op::attributesName() {
+    return EnumStrings<Aidge::Heaviside_Op::Attr>::data;
+}
+
+#undef LIST_HEAVISIDE_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_HEAVISIDE_H_ */
diff --git a/include/aidge/operator/LRN.hpp b/include/aidge/operator/LRN.hpp
index 6c82b6b4670cff44e9d21aeabe8f64aa2b2e2397..b1cbc143dd592271ebb982a81eb9350b0ea04a70 100644
--- a/include/aidge/operator/LRN.hpp
+++ b/include/aidge/operator/LRN.hpp
@@ -23,21 +23,12 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
-enum class LRNAttr {
-    Alpha,  ///< Scale factor for normalization.
-    Beta,   ///< Exponent applied to the normalization term.
-    Bias,   ///< Constant bias added to the normalization term.
-    Size    ///< Number of channels to normalize over.
-};
-} // namespace Aidge
-namespace {
-    /**
-     * @brief EnumStrings specialization for LRNAttr.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::LRNAttr>::data[] = {"alpha", "beta", "bias", "size", nullptr};
-}
+#define LIST_LRN_ATTR(X)          \
+    X(Alpha, "alpha", float),     \
+    X(Beta, "beta", float),       \
+    X(Bias, "bias", float),       \
+    X(Size, "size", std::int32_t)
+
 namespace Aidge {
 /**
  * @brief Description of a Local Response Normalization (LRN) operation on an input Tensor.
@@ -77,9 +68,22 @@ public:
      */
     static const std::string Type;
 
+    /**
+     * @enum Attr
+     * @brief Attributes for the Local Response Normalization (LRN) operation.
+     *
+     * - Alpha: Scale factor for normalization.
+     * - Beta: Exponent applied to the normalization term.
+     * - Bias: Constant bias added to the normalization term.
+     * - Size: Number of channels to normalize over.
+     */
+    enum class Attr {
+        GENERATE_LIST_ATTR_ENUM(LIST_LRN_ATTR)
+    };
+
 private:
-    using Attributes_ = StaticAttributes<LRNAttr, float, float, float, std::int32_t>;
-    template <LRNAttr e> using attr = typename Attributes_::template attr<e>;
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_LRN_ATTR)>;
+    template <Attr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
 public:
@@ -131,25 +135,25 @@ public:
      * @brief Get or modify the `alpha` attribute.
      * @return Reference to the `alpha` attribute.
      */
-    inline float& alpha() const noexcept { return mAttributes->getAttr<LRNAttr::Alpha>(); }
+    inline float& alpha() const noexcept { return mAttributes->getAttr<Attr::Alpha>(); }
 
     /**
      * @brief Get or modify the `beta` attribute.
      * @return Reference to the `beta` attribute.
      */
-    inline float& beta() const noexcept { return mAttributes->getAttr<LRNAttr::Beta>(); }
+    inline float& beta() const noexcept { return mAttributes->getAttr<Attr::Beta>(); }
 
     /**
      * @brief Get or modify the `bias` attribute.
      * @return Reference to the `bias` attribute.
      */
-    inline float& bias() const noexcept { return mAttributes->getAttr<LRNAttr::Bias>(); }
+    inline float& bias() const noexcept { return mAttributes->getAttr<Attr::Bias>(); }
 
     /**
      * @brief Get or modify the `size` attribute.
      * @return Reference to the `size` attribute.
      */
-    inline std::int32_t& size() const noexcept { return mAttributes->getAttr<LRNAttr::Size>(); }
+    inline std::int32_t& size() const noexcept { return mAttributes->getAttr<Attr::Size>(); }
 
     /**
      * @brief Get the input tensor names for the LRN operator.
@@ -171,9 +175,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::LRNAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 };
 
 /**
@@ -187,4 +189,20 @@ std::shared_ptr<Node> LRN(std::int32_t size, const std::string& name = "");
 
 } // namespace Aidge
 
+namespace {
+template <>
+struct EnumStrings<Aidge::LRN_Op::Attr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::LRN_Op::Attr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_LRN_ATTR)
+};
+}
+
+constexpr const char* const* Aidge::LRN_Op::attributesName() {
+    return EnumStrings<Aidge::LRN_Op::Attr>::data;
+}
+
+#undef LIST_LRN_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_LRN_H_ */
diff --git a/include/aidge/operator/LeakyReLU.hpp b/include/aidge/operator/LeakyReLU.hpp
index acf9bae7f4955fee09699f27b7a23c06ce3d670e..867f324d3044cdc8ebd440dfebd5547f6936f47f 100644
--- a/include/aidge/operator/LeakyReLU.hpp
+++ b/include/aidge/operator/LeakyReLU.hpp
@@ -23,19 +23,9 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
-enum class LeakyReLUAttr {
-    /**
-     * @brief Slope for the negative input values.
-     */
-    NegativeSlope
-};
-} // namespace Aidge
-namespace {
-    template <>
-    const char* const EnumStrings<Aidge::LeakyReLUAttr>::data[]
-        = {"negative_slope"};
-    }
+#define LIST_LEAKYRELU_ATTR(X)  \
+    X(NegativeSlope, "negative_slope", float)
+
 namespace Aidge{
 /**
  * @class LeakyReLU_Op
@@ -57,9 +47,19 @@ class LeakyReLU_Op : public OperatorTensor,
 public:
     static const std::string Type;
 
+    /**
+     * @enum LeakyReLUAttr
+     * @brief Attributes for the LeakyReLU operation.
+     *
+     * - NegativeSlope: Slope for the negative input values.
+     */
+    enum class Attr {
+        GENERATE_LIST_ATTR_ENUM(LIST_LEAKYRELU_ATTR)
+    };
+
 private:
-    using Attributes_ = StaticAttributes<LeakyReLUAttr, float>;
-    template <LeakyReLUAttr e> using attr = typename Attributes_::template attr<e>;
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_LEAKYRELU_ATTR)>;
+    template <Attr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
 public:
@@ -77,7 +77,7 @@ public:
         : OperatorTensor(Type, {InputCategory::Data}, 1),
           mAttributes(
             std::make_shared<Attributes_>(
-                attr<LeakyReLUAttr::NegativeSlope>(negativeSlope)))
+                attr<Attr::NegativeSlope>(negativeSlope)))
     {}
 
     /**
@@ -104,7 +104,7 @@ public:
     /**
      * @brief Get the negative slope value.
      */
-    inline float& negativeSlope() const noexcept { return mAttributes -> getAttr<LeakyReLUAttr::NegativeSlope>(); }
+    inline float& negativeSlope() const noexcept { return mAttributes -> getAttr<Attr::NegativeSlope>(); }
 
     /**
      * @brief Get the names of the input tensors.
@@ -126,9 +126,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::LeakyReLUAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 };
 
 /**
@@ -139,6 +137,22 @@ public:
  * @return std::shared_ptr<Node> Node containing the Operator.
  */
 std::shared_ptr<Node> LeakyReLU(float negativeSlope = 0.0f, const std::string& name = "");
+} // namespace Aidge
+
+namespace {
+template <>
+struct EnumStrings<Aidge::LeakyReLU_Op::Attr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::LeakyReLU_Op::Attr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_LEAKYRELU_ATTR)
+};
+}
+
+constexpr const char* const* Aidge::LeakyReLU_Op::attributesName() {
+    return EnumStrings<Attr>::data;
 }
 
+#undef LIST_LEAKYRELU_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_LEAKYRELU_H_ */
diff --git a/include/aidge/operator/MaxPooling.hpp b/include/aidge/operator/MaxPooling.hpp
index 94c786c312d214ea2ff189e1410cfa8841b8f403..01104262147dc461259c3de17e3b3ec3383328b4 100644
--- a/include/aidge/operator/MaxPooling.hpp
+++ b/include/aidge/operator/MaxPooling.hpp
@@ -28,47 +28,47 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
+// Define the X‑macro list with three parameters: name, string, and type.
+
+#define LIST_MAXPOOLING_ATTR(X)                     \
+    X(KernelDims, "kernel_dims", sizeArr_t<DIM>),   \
+    X(StrideDims, "stride_dims", sizeArr_t<DIM>),   \
+    X(Dilations,  "dilations",   sizeArr_t<DIM>),   \
+    X(CeilMode,   "ceil_mode",   bool)
+
 namespace Aidge {
 
 /**
- * @enum MaxPoolingAttr
+ * @enum Attr
  * @brief Attributes defining the configuration of a MaxPooling Operator.
+ *
+ * - **KernelDims**: Kernel dimensions specifying the size of the pooling window for each spatial dimension.
+ *   Must be an array of positive integers. Common examples include [2,2] or [3,3].
+ * - **StrideDims**: Stride dimensions for sliding the pooling window across the input.
+ *   The stride specifies how much the window moves after each operation.
+ *   Must be an array of positive integers. For example, [1,1] or [2,2].
+ * - **Dilations**: Dilation along each spatial axis. Default value is 1 for all axes.
+ *   Must be an array of positive integers. For example, [1,1].
+ * - **CeilMode**: Flag indicating whether to use ceil or floor when calculating output size.
+ *   - `true`: Use `ceil` for output size calculation.
+ *   - `false`: Use `floor` for output size calculation.
  */
 enum class MaxPoolingAttr {
-  /**
-   * @brief Kernel dimensions specifying the size of the pooling window for each spatial dimension.
-   * Must be an array of positive integers. Common examples include [2,2] or [3,3].
-   */
-  KernelDims,
-  /**
-   * @brief Stride dimensions for sliding the pooling window across the input dimensions.
-   * The stride specifies how much the window moves after each operation.
-   * Must be an array of positive integers. For example, [1,1] or [2,2].
-   */
-  StrideDims,
-  /**
-   * @brief Dilation along each spatial axis. Default value is 1 for all axes.
-   * Must be an array of positive integers. For example, [1,1].
-   */
-  Dilations,
-  /**
-   * @brief Flag indicating whether to use ceil or floor when calculating output size.
-   * - `true`: Use `ceil` for output size calculation.
-   * - `false`: Use `floor` for output size calculation.
-   */
-  CeilMode,
+    GENERATE_LIST_ATTR_ENUM(LIST_MAXPOOLING_ATTR)
 };
 } // namespace Aidge
-namespace {
-    /**
-     * @brief String representations of MaxPooling attributes for debugging and logging.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::MaxPoolingAttr>::data[] = {"kernel_dims", "stride_dims", "dilations", "ceil_mode"};
-    }
 
-namespace Aidge{
+namespace {
+template <>
+struct EnumStrings<Aidge::MaxPoolingAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::MaxPoolingAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_MAXPOOLING_ATTR)
+};
+}
 
+namespace Aidge {
 /**
  * @class MaxPooling_Op
  * @tparam DIM Dimensionality of the input tensor (e.g., 1D, 2D, 3D).
@@ -108,10 +108,8 @@ public:
 
 private:
     using Attributes_ = StaticAttributes<MaxPoolingAttr,
-                                         std::array<DimSize_t, DIM>,
-                                         std::array<DimSize_t, DIM>,
-                                         std::array<DimSize_t, DIM>,
-                                         bool>;
+                                GENERATE_LIST_ATTR_TYPE(LIST_MAXPOOLING_ATTR)
+            >;
     template <MaxPoolingAttr e>
     using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes; ///< Shared pointer to operator attributes.
@@ -211,7 +209,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::MaxPoolingAttr>::data;
 	}
 };
@@ -263,5 +261,6 @@ inline std::shared_ptr<Node> MaxPooling(
 
 }  // namespace Aidge
 
+#undef LIST_MAXPOOLING_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_MAXPOOLING_H_ */
diff --git a/include/aidge/operator/Memorize.hpp b/include/aidge/operator/Memorize.hpp
index 59df17ec146bb33dc1e6e8c007eb275054fd727b..e1eea4a284f494553708fa56f99477162eab93ab 100644
--- a/include/aidge/operator/Memorize.hpp
+++ b/include/aidge/operator/Memorize.hpp
@@ -114,24 +114,13 @@ public:
      */
     void forward() override;
 };
-
-enum class MemorizeAttr {
-    ScheduleStep,   // Defines the step interval for scheduling memory updates.
-    ForwardStep,    // Tracks the current step in the forward pass.
-    EndStep         // The final step for which memory updates will occur.
-};
 } // namespace Aidge
-namespace {
-    /**
-     * @brief String representations of the Memorize operator's attributes.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::MemorizeAttr>::data[] = {
-        "schedule_step",
-        "forward_step",
-        "end_step"
-    };
-}
+
+#define LIST_MEMORIZE_ATTR(X)                        \
+    X(ScheduleStep, "schedule_step", std::uint32_t), \
+    X(ForwardStep, "forward_step", std::uint32_t),   \
+    X(EndStep, "end_step", std::uint32_t)
+
 namespace Aidge {
 /**
  * @class Memorize_Op
@@ -155,9 +144,21 @@ class Memorize_Op : public OperatorTensor,
 public:
     static const std::string Type;
 
+    /**
+     * @enum Attr
+     * @brief Attributes for the Memorize operation.
+     *
+     * - ScheduleStep: Defines the step interval for scheduling memory updates.
+     * - ForwardStep: Tracks the current step in the forward pass.
+     * - EndStep: The final step for which memory updates will occur.
+     */
+    enum class Attr {
+        GENERATE_LIST_ATTR_ENUM(LIST_MEMORIZE_ATTR)
+    };
+
 private:
-    using Attributes_ = StaticAttributes<MemorizeAttr, std::uint32_t, std::uint32_t, std::uint32_t>;
-    template <MemorizeAttr e>
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_MEMORIZE_ATTR)>;
+    template <Attr e>
     using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -223,19 +224,19 @@ public:
      * @brief Get or set the scheduling step for the operator.
      * @return A reference to the scheduling step.
      */
-    inline std::uint32_t& scheduleStep() const { return mAttributes->template getAttr<MemorizeAttr::ScheduleStep>(); }
+    inline std::uint32_t& scheduleStep() const { return mAttributes->template getAttr<Attr::ScheduleStep>(); }
 
     /**
      * @brief Get or set the forward step counter for the operator.
      * @return A reference to the forward step counter.
      */
-    inline std::uint32_t& forwardStep() const { return mAttributes->template getAttr<MemorizeAttr::ForwardStep>(); }
+    inline std::uint32_t& forwardStep() const { return mAttributes->template getAttr<Attr::ForwardStep>(); }
 
     /**
      * @brief Get or set the end step defining the memory duration.
      * @return A reference to the end step value.
      */
-    inline std::uint32_t& endStep() const { return mAttributes->template getAttr<MemorizeAttr::EndStep>(); }
+    inline std::uint32_t& endStep() const { return mAttributes->template getAttr<Attr::EndStep>(); }
 
     /**
      * @brief Retrieve the names of the operator's input tensors.
@@ -257,9 +258,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::MemorizeAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 };
 
 /**
@@ -271,5 +270,20 @@ public:
 std::shared_ptr<Node> Memorize(const std::uint32_t endStep, const std::string& name = "");
 }  // namespace Aidge
 
+namespace {
+template <>
+struct EnumStrings<Aidge::Memorize_Op::Attr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::Memorize_Op::Attr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_MEMORIZE_ATTR)
+};
+}
+
+constexpr const char* const* Aidge::Memorize_Op::attributesName() {
+    return EnumStrings<Aidge::Memorize_Op::Attr>::data;
+}
+
+#undef LIST_MEMORIZE_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_MEMORIZE_H_ */
diff --git a/include/aidge/operator/Operator.hpp b/include/aidge/operator/Operator.hpp
index dd59af175231acb274126d7f396cdd502046b004..81a54620a6f325eba04e9055e12d73d6a5d64163 100644
--- a/include/aidge/operator/Operator.hpp
+++ b/include/aidge/operator/Operator.hpp
@@ -31,6 +31,16 @@
 #ifdef PYBIND
 namespace py = pybind11;
 #endif
+
+
+#define SELECT_ENUM_FOR_ATTR(name, str, type) name
+#define SELECT_STR_FOR_ATTR(name, str, type) str
+#define SELECT_TYPE_FOR_ATTR(name, str, type) type
+
+#define GENERATE_LIST_ATTR_ENUM(LIST_ATTRS) LIST_ATTRS(SELECT_ENUM_FOR_ATTR)
+#define GENERATE_LIST_ATTR_STR(LIST_ATTRS) LIST_ATTRS(SELECT_STR_FOR_ATTR)
+#define GENERATE_LIST_ATTR_TYPE(LIST_ATTRS) LIST_ATTRS(SELECT_TYPE_FOR_ATTR)
+
 namespace Aidge {
 
 /**
diff --git a/include/aidge/operator/Pad.hpp b/include/aidge/operator/Pad.hpp
index 0880b2c97ed7e2e6e9e4515c82c37aa4e0e91233..491a8a3697de5c685d6c9130423dd290e4b6cf71 100644
--- a/include/aidge/operator/Pad.hpp
+++ b/include/aidge/operator/Pad.hpp
@@ -24,17 +24,25 @@
 #include "aidge/utils/ArrayHelpers.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
+#define LIST_PAD_ATTR(X)                                                \
+    X(BeginEndBorders, "begin_end_borders", Aidge::sizeArr_t<2 * DIM>), \
+    X(BorderType, "border_type", PadBorderType),                        \
+    X(BorderValue, "border_value", double)
+
 
+namespace Aidge {
 /**
  * @enum PadAttr
  * @brief Attributes for the Pad operator.
+ *
+ * - BeginEndBorders: Specifies the padding sizes for the beginning and end of each dimension.
+ * - BorderType: Type of border handling during padding.
+ * - BorderValue: Value to be used for constant padding.
  */
 enum class PadAttr {
-    BeginEndBorders, ///< Specifies the padding sizes for the beginning and end of each dimension.
-    BorderType,      ///< Type of border handling during padding.
-    BorderValue      ///< Value to be used for constant padding.
+    GENERATE_LIST_ATTR_ENUM(LIST_PAD_ATTR)
 };
+
 /**
  * @enum PadBorderType
  * @brief Types of border handling available for padding.
@@ -46,19 +54,16 @@ enum class PadBorderType {
     Wrap,     ///< Values wrap around the tensor dimensions.
     Zero      ///< All out-of-bound values are set to 0.
 };
-
 } // namespace Aidge
 
 namespace {
-    /**
-     * @brief EnumStrings specialization for PadAttr.
-     */
-    template <>
-    const char* const EnumStrings<Aidge::PadAttr>::data[] = {
-        "begin_end_borders",
-        "border_type",
-        "border_value"
-    };
+template <>
+struct EnumStrings<Aidge::PadAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::PadAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_PAD_ATTR)
+};
 
 /**
  * @brief EnumStrings specialization for PadBorderType.
@@ -131,11 +136,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<PadAttr,
-                                         std::array<DimSize_t, 2 * DIM>, // Padding for start and end of each dimension.
-                                         PadBorderType,                  // Border handling type.
-                                         double                          // Border value for constant padding.
-                                         >;
+    using Attributes_ = StaticAttributes<PadAttr, GENERATE_LIST_ATTR_TYPE(LIST_PAD_ATTR)>;
     template <PadAttr e>
     using attr = typename Attributes_::template attr<e>;
 
@@ -247,7 +248,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::PadAttr>::data;
 	}
 };
@@ -284,6 +285,6 @@ inline std::shared_ptr<Node> Pad(
 extern template class Aidge::Pad_Op<1>;
 extern template class Aidge::Pad_Op<2>;
 
-
+#undef LIST_PAD_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_PAD_H_ */
diff --git a/include/aidge/operator/Pop.hpp b/include/aidge/operator/Pop.hpp
index d9d52f9bcd07a671d68e3db53c378c9ee6659c8e..9790f05e9375435f7adf2dfbf3fe0460487416fc 100644
--- a/include/aidge/operator/Pop.hpp
+++ b/include/aidge/operator/Pop.hpp
@@ -92,25 +92,35 @@ public:
      */
     void backward() override;
 };
+} //namespace Aidge
 
+#define LIST_POP_ATTR(X)  \
+    X(ForwardStep, "forward_step", std::uint32_t),  \
+    X(BackwardStep, "backward_step", std::uint32_t)
+
+namespace Aidge {
 /**
  * @enum PopAttr
  * @brief Attributes specific to the `Pop` operator.
+ *
+ * - ForwardStep: Tracks the current step in the forward pass.
+ * - BackwardStep: Tracks the current step in the backward pass.
  */
 enum class PopAttr {
-    ForwardStep,    // Tracks the current step in the forward pass
-    BackwardStep    // Tracks the current step in the backward pass
+    GENERATE_LIST_ATTR_ENUM(LIST_POP_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-    /**
-     * @brief String representations of the `Pop` operator's attributes.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::PopAttr>::data[] = {
-        "forward_step", "backward_step"
-    };
+template <>
+struct EnumStrings<Aidge::PopAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::PopAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_POP_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @class Pop_Op
@@ -131,7 +141,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<PopAttr, std::uint32_t, std::uint32_t>;
+    using Attributes_ = StaticAttributes<PopAttr, GENERATE_LIST_ATTR_TYPE(LIST_POP_ATTR)>;
     template <PopAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -226,7 +236,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::PopAttr>::data;
 	}
 };
@@ -239,5 +249,6 @@ public:
 std::shared_ptr<Node> Pop(const std::string& name = "");
 }  // namespace Aidge
 
+#undef LIST_POP_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_POP_H_ */
diff --git a/include/aidge/operator/Producer.hpp b/include/aidge/operator/Producer.hpp
index 3690579d34373b64eec20042b7f9615266c15aee..ae88c0c714ec5a1ce3a5b39e290d2566e49d9f4b 100644
--- a/include/aidge/operator/Producer.hpp
+++ b/include/aidge/operator/Producer.hpp
@@ -28,21 +28,29 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 
-namespace Aidge {
 
+#define LIST_PRODUCER_ATTR(X) X(Constant, "constant", bool)
+
+namespace Aidge {
 /**
- * @enum ProdAttr
+ * @enum ProducerAttr
  * @brief Attributes specific to the `Producer_Op` class.
  */
-enum class ProdAttr { Constant };
+enum class ProducerAttr {
+    GENERATE_LIST_ATTR_ENUM(LIST_PRODUCER_ATTR)
+};
 } // namespace Aidge
+
 namespace {
-    /**
-     * @brief Enum string representation for `ProdAttr`.
-     */
-    template <>
-    const char* const EnumStrings<Aidge::ProdAttr>::data[] = {"constant"};
+template <>
+struct EnumStrings<Aidge::ProducerAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ProducerAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_PRODUCER_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @class Producer_Op
@@ -76,8 +84,10 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<ProdAttr, bool>;
-    template <ProdAttr e> using attr = typename Attributes_::template attr<e>;
+    using Attributes_ = StaticAttributes<ProducerAttr,
+            GENERATE_LIST_ATTR_TYPE(LIST_PRODUCER_ATTR)
+        >;
+    template <ProducerAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
 public:
@@ -160,7 +170,7 @@ public:
      *
      * @return A reference to the constant attribute.
      */
-    inline bool& constant() const { return mAttributes->template getAttr<ProdAttr::Constant>(); }
+    inline bool& constant() const { return mAttributes->template getAttr<ProducerAttr::Constant>(); }
 
     /**
      * @brief Performs the forward operation for the operator.
@@ -280,4 +290,6 @@ std::shared_ptr<Node> addProducer(std::shared_ptr<Node>& otherNode, const IOInde
 
 } // namespace Aidge
 
+#undef LIST_PRODUCER_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_PRODUCER_H_ */
diff --git a/include/aidge/operator/ReduceMean.hpp b/include/aidge/operator/ReduceMean.hpp
index 3ee4a1bec40f7f6aa409308708bc3338174c652b..cdb139f96f4bb33b9a22479a2f996d71abf85f0e 100644
--- a/include/aidge/operator/ReduceMean.hpp
+++ b/include/aidge/operator/ReduceMean.hpp
@@ -25,41 +25,11 @@
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
-enum class ReduceMeanAttr {
-  /**
-   * @brief Axes over which the mean operation is performed.
-   *
-   * Axes are specified as a vector of integers, each representing a dimension
-   * of the input tensor to be reduced.
-   */
-  Axes,
-
-  /**
-   * @brief Flag indicating whether to keep reduced dimensions.
-   *
-   * - `true`: Retain reduced dimensions with size 1.
-   * - `false`: Completely remove reduced dimensions.
-   */
-  KeepDims,
-
-  /**
-   * @brief Flag indicating behavior when axes are empty.
-   *
-   * - `true`: No operation is performed if axes are empty.
-   * - `false`: Reduction is performed over all axes if none are specified.
-   */
-  NoopWithEmptyAxes
-};
-} // namespace Aidge
-namespace {
-    template <>
-    const char *const EnumStrings<Aidge::ReduceMeanAttr>::data[] = {
-        "axes",
-        "keep_dims",
-        "noop_with_empty_axes"
-    };
-}
+#define LIST_REDUCEMEAN_ATTR(X)  \
+    X(Axes, "axes", std::vector<std::int32_t>),  \
+    X(KeepDims, "keep_dims", bool),  \
+    X(NoopWithEmptyAxes, "noop_with_empty_axes", bool)
+
 namespace Aidge {
 /**
  * @class ReduceMean_Op
@@ -94,13 +64,26 @@ class ReduceMean_Op : public OperatorTensor,
 public:
     static const std::string Type;
 
+    /**
+     * @enum Attr
+     * @brief Defines attributes for the ReduceMean operation.
+     *
+     * - **Axes**: Specifies the dimensions along which the mean is computed.
+     * - **KeepDims**: Determines whether the reduced dimensions are preserved.
+     *   - `true`: Retains reduced dimensions with a size of 1.
+     *   - `false`: Removes reduced dimensions from the output.
+     * - **NoopWithEmptyAxes**: Defines behavior when no axes are provided.
+     *   - `true`: The operation is skipped if no axes are specified.
+     *   - `false`: The reduction is applied across all dimensions.
+     */
+    enum class Attr {
+        GENERATE_LIST_ATTR_ENUM(LIST_REDUCEMEAN_ATTR)
+    };
+
 private:
-    using Attributes_ = StaticAttributes<ReduceMeanAttr,
-                                            std::vector<std::int32_t>,
-                                            bool,
-                                            bool>;
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_REDUCEMEAN_ATTR)>;
 
-    template <ReduceMeanAttr e>
+    template <Attr e>
     using attr = typename Attributes_::template attr<e>;
 
     const std::shared_ptr<Attributes_> mAttributes;
@@ -154,17 +137,17 @@ public:
     /**
      * @brief Get the axes over which the mean is computed.
      */
-    inline std::vector<std::int32_t>& axes() const noexcept { return mAttributes -> getAttr<ReduceMeanAttr::Axes>(); }
+    inline std::vector<std::int32_t>& axes() const noexcept { return mAttributes -> getAttr<Attr::Axes>(); }
 
     /**
      * @brief Get whether reduced dimensions are retained.
      */
-    inline bool& keepDims() const noexcept { return mAttributes -> getAttr<ReduceMeanAttr::KeepDims>(); }
+    inline bool& keepDims() const noexcept { return mAttributes -> getAttr<Attr::KeepDims>(); }
 
     /**
      * @brief Get the behavior when axes are empty.
      */
-    inline bool& noopWithEmptyAxes() const noexcept { return mAttributes -> getAttr<ReduceMeanAttr::NoopWithEmptyAxes>(); }
+    inline bool& noopWithEmptyAxes() const noexcept { return mAttributes -> getAttr<Attr::NoopWithEmptyAxes>(); }
 
     static const std::vector<std::string> getInputsName() {
         return {"data_input"};
@@ -178,9 +161,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::ReduceMeanAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 
     virtual ~ReduceMean_Op() noexcept;
 };
@@ -203,5 +184,20 @@ std::shared_ptr<Node> ReduceMean(const std::vector<std::int32_t> &axes,
 
 }  // namespace Aidge
 
+namespace {
+template <>
+struct EnumStrings<Aidge::ReduceMean_Op::Attr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ReduceMean_Op::Attr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_REDUCEMEAN_ATTR)
+};
+}
+
+constexpr const char* const* Aidge::ReduceMean_Op::attributesName(){
+    return EnumStrings<Aidge::ReduceMean_Op::Attr>::data;
+}
+
+#undef LIST_REDUCEMEAN_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_REDUCEMEAN_H_ */
diff --git a/include/aidge/operator/ReduceSum.hpp b/include/aidge/operator/ReduceSum.hpp
index adb58f895cf3fbfa67b84c518a7f6cedf09d1a19..73f59c25d43e8c78cfd9feb42eefcfd94f8680a1 100644
--- a/include/aidge/operator/ReduceSum.hpp
+++ b/include/aidge/operator/ReduceSum.hpp
@@ -19,44 +19,16 @@
 
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/OperatorTensor.hpp"
-#include "aidge/operator/Producer.hpp"
 #include "aidge/utils/ErrorHandling.hpp"
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
-enum class ReduceSumAttr {
-/**
-   * @brief Axes over which the mean operation is performed.
-   *
-   * Axes are specified as a vector of integers, each representing a dimension
-   * of the input tensor to be reduced.
-   */
-  Axes,
-
-  /**
-   * @brief Flag indicating whether to keep reduced dimensions.
-   *
-   * - `true`: Retain reduced dimensions with size 1.
-   * - `false`: Completely remove reduced dimensions.
-   */
-  KeepDims,
-
-  /**
-   * @brief Flag indicating behavior when axes are empty.
-   *
-   * - `true`: No operation is performed if axes are empty.
-   * - `false`: Reduction is performed over all axes if none are specified.
-   */
-  NoopWithEmptyAxes
-};
+#define LIST_REDUCESUM_ATTR(X)  \
+    X(Axes, "axes", std::vector<std::int32_t>),  \
+    X(KeepDims, "keep_dims", bool),  \
+    X(NoopWithEmptyAxes, "noop_with_empty_axes", bool)
 
-} // namespace Aidge
-namespace {
-    template <>
-    const char *const EnumStrings<Aidge::ReduceSumAttr>::data[] = {"axes", "keep_dims", "noop_with_empty_axes"};
-}
 namespace Aidge {
 /**
  * @class ReduceSum_Op
@@ -91,12 +63,25 @@ class ReduceSum_Op : public OperatorTensor,
 public:
     static const std::string Type;
 
+    /**
+     * @enum Attr
+     * @brief Defines attributes for the ReduceSum operation.
+     *
+     * - **Axes**: Specifies the dimensions along which the sum is computed.
+     * - **KeepDims**: Determines whether the reduced dimensions are preserved.
+     *   - `true`: Retains reduced dimensions with a size of 1.
+     *   - `false`: Removes reduced dimensions from the output.
+     * - **NoopWithEmptyAxes**: Defines behavior when no axes are provided.
+     *   - `true`: The operation is skipped if no axes are specified.
+     *   - `false`: The reduction is applied across all dimensions.
+     */
+    enum class Attr {
+        GENERATE_LIST_ATTR_ENUM(LIST_REDUCESUM_ATTR)
+    };
+
 private:
-    using Attributes_ = StaticAttributes<ReduceSumAttr,
-                                            std::vector<std::int32_t>,
-                                            bool,
-                                            bool>;
-    template <ReduceSumAttr e>
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_REDUCESUM_ATTR)>;
+    template <Attr e>
     using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -114,9 +99,9 @@ public:
     ReduceSum_Op(const std::vector<std::int32_t>& axes, bool keep_dims, bool noop_with_empty_axes)
         : OperatorTensor(Type, {InputCategory::Data}, 1),
           mAttributes(std::make_shared<Attributes_>(
-            attr<ReduceSumAttr::Axes>(axes),
-            attr<ReduceSumAttr::KeepDims>(keep_dims),
-            attr<ReduceSumAttr::NoopWithEmptyAxes>(noop_with_empty_axes)))
+            attr<Attr::Axes>(axes),
+            attr<Attr::KeepDims>(keep_dims),
+            attr<Attr::NoopWithEmptyAxes>(noop_with_empty_axes)))
     {}
 
     /**
@@ -157,17 +142,17 @@ public:
     /**
      * @brief Get the axes over which the mean is computed.
      */
-    inline std::vector<std::int32_t>& axes() const noexcept { return mAttributes -> getAttr<ReduceSumAttr::Axes>(); }
+    inline std::vector<std::int32_t>& axes() const noexcept { return mAttributes -> getAttr<Attr::Axes>(); }
 
     /**
      * @brief Get whether reduced dimensions are retained.
      */
-    inline bool& keepDims() const noexcept { return mAttributes -> getAttr<ReduceSumAttr::KeepDims>(); }
+    inline bool& keepDims() const noexcept { return mAttributes -> getAttr<Attr::KeepDims>(); }
 
     /**
      * @brief Get the behavior when axes are empty.
      */
-    inline bool& noopWithEmptyAxes() const noexcept { return mAttributes -> getAttr<ReduceSumAttr::NoopWithEmptyAxes>(); }
+    inline bool& noopWithEmptyAxes() const noexcept { return mAttributes -> getAttr<Attr::NoopWithEmptyAxes>(); }
 
 
     static const std::vector<std::string> getInputsName() {
@@ -181,9 +166,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::ReduceSumAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 };
 
 /**
@@ -208,4 +191,20 @@ inline std::shared_ptr<Node> ReduceSum(const std::vector<std::int32_t> &axes={},
 }
 }  // namespace Aidge
 
+namespace {
+template <>
+struct EnumStrings<Aidge::ReduceSum_Op::Attr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ReduceSum_Op::Attr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_REDUCESUM_ATTR)
+};
+}
+
+constexpr const char* const* Aidge::ReduceSum_Op::attributesName() {
+    return EnumStrings<Aidge::ReduceSum_Op::Attr>::data;
+}
+
+#undef LIST_REDUCESUM_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_REDUCESUM_H_ */
diff --git a/include/aidge/operator/Reshape.hpp b/include/aidge/operator/Reshape.hpp
index e69c42d4d98974e7bb00acbf17581cd56ada1331..f02dae45e8285e7187ca7f739c163e69bee7c81c 100644
--- a/include/aidge/operator/Reshape.hpp
+++ b/include/aidge/operator/Reshape.hpp
@@ -43,32 +43,37 @@ public:
     void forward() override;
     void backward() override;
 };
+} // namespace Aidge
+
+
+#define LIST_RESHAPE_ATTR(X)  \
+    X(Shape, "shape", std::vector<std::int64_t>),  \
+    X(AllowZero, "allow_zero", bool)
 
+
+namespace Aidge {
 /**
  * @enum ReshapeAttr
  * @brief Enumeration of attributes specific to the Reshape operator.
+ *
+ * - **Shape**: The target shape for the output tensor.
+ * - **AllowZero**: When true, zeros in the target shape retain the corresponding dimension size from the input tensor.
  */
 enum class ReshapeAttr {
-    /**
-     * @brief The target shape for the output tensor.
-     */
-    Shape,
-
-    /**
-     * @brief Whether zeros in the shape attribute are allowed.
-     *
-     * When true, zeros in the target shape retain the corresponding dimension size from the input tensor.
-     */
-    AllowZero
+    GENERATE_LIST_ATTR_ENUM(LIST_RESHAPE_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-    /**
-     * @brief EnumStrings specialization for ReshapeAttr.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::ReshapeAttr>::data[] = {"shape", "allow_zero"};
+template <>
+struct EnumStrings<Aidge::ReshapeAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ReshapeAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_RESHAPE_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief Description of Reshape operator that adjusts the shape of the input tensor.
@@ -94,7 +99,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<ReshapeAttr, std::vector<std::int64_t>, bool>;
+    using Attributes_ = StaticAttributes<ReshapeAttr, GENERATE_LIST_ATTR_TYPE(LIST_RESHAPE_ATTR)>;
     template <ReshapeAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -189,7 +194,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::ReshapeAttr>::data;
 	}
 };
@@ -208,5 +213,6 @@ std::shared_ptr<Node> Reshape(const std::vector<std::int64_t>& shape = {},
 
 }  // namespace Aidge
 
+#undef LIST_RESHAPE_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_RESHAPE_H_ */
diff --git a/include/aidge/operator/Resize.hpp b/include/aidge/operator/Resize.hpp
index 37d42fcc861db42c991a6e7f4296d725d002aad5..32ddbe48804e359a9a868a149e66c43342b76d56 100644
--- a/include/aidge/operator/Resize.hpp
+++ b/include/aidge/operator/Resize.hpp
@@ -25,30 +25,38 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
-namespace Aidge {
 
-/* @brief attributes for the aidge operator */
+#define LIST_RESIZE_ATTR(X) \
+    X(CoordinateTransformationMode, "coordinate_transformation_mode", Interpolation::CoordinateTransformation), \
+    X(CubicCoeffA, "cubic_coeff_a", float), \
+    X(InterpolationMode, "interpolation_mode", Interpolation::Mode), \
+    X(PaddingMode, "padding_mode", PadBorderType)
+
+namespace Aidge {
+/**
+ * @enum ResizeAttr
+ * @brief Attributes for the Resize operation.
+ *
+ * - CoordinateTransformationMode: Defines how source coordinates map to target coordinates.
+ * - CubicCoeffA: Coefficient used in cubic interpolation.
+ * - InterpolationMode: Defines the interpolation method used.
+ * - PaddingMode: Specifies how padding is handled.
+ */
 enum class ResizeAttr {
-    //   antialias,
-    // axes,
-    CoordinateTransformationMode,
-    CubicCoeffA,
-    // excludeOutside,
-    //   extrapolation_value,
-    //   keep_aspect_ratio_policy,
-    InterpolationMode,
-    PaddingMode
+    GENERATE_LIST_ATTR_ENUM(LIST_RESIZE_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-    template <>
-    const char *const EnumStrings<Aidge::ResizeAttr>::data[] = {
-        "coordinate_transformation_mode",
-        "cubic_coeff_a",
-        "interpolation_mode",
-        "padding_mode"
-    };
+template <>
+struct EnumStrings<Aidge::ResizeAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ResizeAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_RESIZE_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief Resize operator, will up/downscale a given tensor given the input.
@@ -98,18 +106,15 @@ class Resize_Op
           std::string,
           std::function<std::shared_ptr<OperatorImpl>(const Resize_Op &)>> {
 
-  private:
+private:
     using Attributes_ =
         StaticAttributes<ResizeAttr,
-                         Interpolation::CoordinateTransformation,
-                         float,
-                         Interpolation::Mode,
-                         PadBorderType>;
+                         GENERATE_LIST_ATTR_TYPE(LIST_RESIZE_ATTR)>;
     template <ResizeAttr e>
     using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
-  public:
+public:
     static const std::string Type;
     /**
      * @brief creates a resize operator
@@ -206,7 +211,7 @@ class Resize_Op
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::ResizeAttr>::data;
 	}
 };
@@ -240,4 +245,6 @@ Resize(std::vector<float> scale = std::vector<float>(),
 
 } // namespace Aidge
 
+#undef LIST_RESIZE_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_RESIZE_H_ */
diff --git a/include/aidge/operator/Scaling.hpp b/include/aidge/operator/Scaling.hpp
index fb342d34580092febaf3d1e63ea78247c3e8f77a..c5264fe551bf6ab0d18010b37bb66782170cee74 100644
--- a/include/aidge/operator/Scaling.hpp
+++ b/include/aidge/operator/Scaling.hpp
@@ -26,37 +26,35 @@
 // Caution: This operator is now deprecated and should no longer be used.
 // It has been replaced by the MetaOperator "Quantizer" (located directly in aidge_quantization).
 
+#define LIST_SCALING_ATTR(X) \
+    X(ScalingFactor, "scaling_factor", float), \
+    X(QuantizedNbBits, "quantized_nb_bits", std::size_t), \
+    X(IsOutputUnsigned, "is_output_unsigned", bool)
+
 namespace Aidge {
+/**
+ * @enum ScalingAttr
+ * @brief Attributes for the Scaling operation.
+ *
+ * - ScalingFactor: Floating-point scaling factor applied to the input tensor.
+ * - QuantizedNbBits: Specifies the bit-width used for quantization.
+ * - IsOutputUnsigned: Indicates whether the quantized output values are unsigned.
+ */
 enum class ScalingAttr {
-    /**
-     * @brief Scaling factor applied to the input tensor.
-     *
-     * This floating-point value is used to scale the input tensor.
-     */
-    ScalingFactor,
-
-    /**
-     * @brief Number of quantization bits.
-     *
-     * Specifies the bit-width used for quantization.
-     * For example, a value of `8` represents 8-bit quantization.
-     */
-    QuantizedNbBits,
-
-    /**
-     * @brief Indicates whether the output is unsigned.
-     *
-     * - `true`: The quantized output values are unsigned integers.
-     * - `false`: The quantized output values are signed integers.
-     */
-    IsOutputUnsigned
+    GENERATE_LIST_ATTR_ENUM(LIST_SCALING_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-    template <>
-    const char* const EnumStrings<Aidge::ScalingAttr>::data[]
-        = {"scaling_factor", "quantized_nb_bits", "is_output_unsigned"};
+template <>
+struct EnumStrings<Aidge::ScalingAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ScalingAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_SCALING_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief Description of a scaling operation to scale and quantize input tensors.
@@ -82,7 +80,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<ScalingAttr, float, std::size_t, bool>;
+    using Attributes_ = StaticAttributes<ScalingAttr, GENERATE_LIST_ATTR_TYPE(LIST_SCALING_ATTR)>;
     template <ScalingAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -145,7 +143,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::ScalingAttr>::data;
 	}
 };
@@ -165,5 +163,6 @@ std::shared_ptr<Node> Scaling(float scalingFactor = 1.0f,
                                      const std::string& name = "");
 } // namespace Aidge
 
+#undef LIST_SCALING_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_SCALING_H_ */
diff --git a/include/aidge/operator/Shape.hpp b/include/aidge/operator/Shape.hpp
index 2a553fb827fc8a8d4b03fa06ebcd8825ae2ed64f..290d95eefd7972dad3d0ed05a01eb7105f5f9a62 100644
--- a/include/aidge/operator/Shape.hpp
+++ b/include/aidge/operator/Shape.hpp
@@ -47,29 +47,36 @@ public:
      */
     void forward() override;
 };
+}
+
+#define LIST_SHAPE_ATTR(X) \
+    X(Start, "start", std::int64_t), \
+    X(End, "end", std::int64_t)
 
+namespace Aidge {
 /**
  * @enum ShapeAttr
  * @brief Enumeration of attributes specific to the Shape operator.
+ *
+ * - Start: Start index of the slice of dimensions to return.
+ * - End: End index of the slice of dimensions to return (exclusive).
  */
 enum class ShapeAttr {
-    /**
-     * @brief Start index of the slice of dimensions to return.
-     */
-    Start,
-    /**
-     * End index of the slice of dimensions to return (exclusive).
-     */
-    End
+    GENERATE_LIST_ATTR_ENUM(LIST_SHAPE_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-    /**
-     * @brief EnumStrings specialization for ShapeAttr.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::ShapeAttr>::data[] = {"start", "end"};
+/// @brief EnumStrings specialization for ShapeAttr.
+template <>
+struct EnumStrings<Aidge::ShapeAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::ShapeAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_SHAPE_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief Description of the operation of extracting the shape of a tensor.
@@ -92,7 +99,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<ShapeAttr, std::int64_t, std::int64_t>;
+    using Attributes_ = StaticAttributes<ShapeAttr, GENERATE_LIST_ATTR_TYPE(LIST_SHAPE_ATTR)>;
     template <ShapeAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -176,7 +183,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::ShapeAttr>::data;
 	}
 };
@@ -193,6 +200,6 @@ std::shared_ptr<Node> Shape(const std::int64_t start = 0, const std::int64_t end
 
 } // namespace Aidge
 
-
+#undef LIST_SHAPE_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_SHAPE_H_ */
diff --git a/include/aidge/operator/Slice.hpp b/include/aidge/operator/Slice.hpp
index fa21b3d197551e54a95fe29dbb8e3f83d30865af..b425fe75208b37105ce6baadd4f2ff63f94f2f3c 100644
--- a/include/aidge/operator/Slice.hpp
+++ b/include/aidge/operator/Slice.hpp
@@ -44,51 +44,43 @@ public:
      */
     void forward() override;
 };
+}  // namespace Aidge
+
+#define LIST_SLICE_ATTR(X) \
+    X(Starts, "starts", std::vector<std::int64_t>), \
+    X(Ends, "ends", std::vector<std::int64_t>), \
+    X(Axes, "axes", std::vector<std::int8_t>), \
+    X(Steps, "steps", std::vector<std::int64_t>)
 
+namespace Aidge {
 /**
  * @enum SliceAttr
  * @brief Attributes for the Slice operation.
+ *
+ * - Starts: Starting indices for the slice along each axis.
+ *   - If index is < 0, the input tensor's rank is added.
+ *   - If index is still < 0, it is forced to 0.
+ *   - If index > dim, it is forced to dim.
+ * - Ends: Ending indices for the slice along each axis (exclusive).
+ *   - Follows the same adjustment rules as Starts.
+ * - Axes: Axes along which the slice operation is performed.
+ * - Steps: Steps to move between each slice along each axis.
  */
 enum class SliceAttr {
-    /**
-     * @brief Starting indices for the slice along each axis.
-     *
-     * Specifies the start position for slicing for each axis.
-     * @details if index is < 0 then the input tansor's rank is added.
-     * After, if index is < 0 then it is forced to 0,
-     * if index > dim then index is forced to dim.
-     */
-    Starts,
-
-    /**
-     * @brief Ending indices for the slice along each axis.
-     *
-     * Specifies the end position (exclusive) for slicing for each axis.
-     * @details if index is < 0 then the input tansor's rank is added.
-     * After, if index is < 0 then it is forced to 0,
-     * if index > dim then index is forced to dim.
-     */
-    Ends,
-
-    /**
-     * @brief Axes along which the slice operation is performed.
-     *
-     * Specifies which dimensions of the input tensor are affected by the slice.
-     */
-    Axes,
-
-    /**
-     * @brief Steps to move between each slice along each axis.
-     *
-     * Specifies the step size for slicing along each axis.
-     */
-    Steps
+    GENERATE_LIST_ATTR_ENUM(LIST_SLICE_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-    template <>
-    const char *const EnumStrings<Aidge::SliceAttr>::data[] = { "starts", "ends", "axes", "steps" };
+template <>
+struct EnumStrings<Aidge::SliceAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::SliceAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_SLICE_ATTR)
+};
 }
+
 namespace Aidge{
 /**
  * @class Slice_Op
@@ -125,19 +117,10 @@ class Slice_Op : public OperatorTensor,
 public:
     static const std::string Type;
 
-    /**
-     * @brief Defines static attributes for the Slice operator.
-     */
-    using Attributes_ = StaticAttributes<SliceAttr,
-                                            std::vector<std::int64_t>, // Starts
-                                            std::vector<std::int64_t>, // Ends
-                                            std::vector<std::int8_t>,  // Axes
-                                            std::vector<std::int64_t>>; // Steps
-
 private:
+    using Attributes_ = StaticAttributes<SliceAttr, GENERATE_LIST_ATTR_TYPE(LIST_SLICE_ATTR)>;
     template <SliceAttr e>
     using attr = typename Attributes_::template attr<e>;
-
     const std::shared_ptr<Attributes_> mAttributes;
 
 public:
@@ -213,7 +196,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::SliceAttr>::data;
 	}
 };
@@ -236,4 +219,6 @@ std::shared_ptr<Node> Slice(const std::vector<std::int64_t>& starts = {},
 
 }  // namespace Aidge
 
+#undef LIST_SLICE_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_SLICE_H_ */
diff --git a/include/aidge/operator/Softmax.hpp b/include/aidge/operator/Softmax.hpp
index 86e1a57e70c4b7070b9af279980b2d5344a2f6f0..b0c6a2edae7ab9bec4a5f45746f2bc9258b6eb29 100644
--- a/include/aidge/operator/Softmax.hpp
+++ b/include/aidge/operator/Softmax.hpp
@@ -23,24 +23,36 @@
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
 
+#define LIST_SOFTMAX_ATTR(X) \
+    X(Axis, "axis", std::int32_t)
+
 namespace Aidge {
+/**
+ * @enum SoftmaxAttr
+ * @brief Attributes for the Softmax operation.
+ *
+ * - Axis: Axis along which the softmax operation is applied.
+ *   - Determines the dimension in the input tensor over which the softmax
+ *     operation will compute normalized exponential values.
+ */
 enum class SoftmaxAttr {
-    /**
-     * @brief Axis along which the softmax operation is applied.
-     *
-     * Determines the dimension in the input tensor over which the softmax
-     * operation will compute normalized exponential values.
-     */
-    Axis
+    GENERATE_LIST_ATTR_ENUM(LIST_SOFTMAX_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-    /**
-     * @brief EnumStrings specialization for SoftmaxAttr.
-     */
-    template <>
-    const char* const EnumStrings<Aidge::SoftmaxAttr>::data[] = {"axis"};
+/**
+ * @brief EnumStrings specialization for SoftmaxAttr.
+ */
+template <>
+struct EnumStrings<Aidge::SoftmaxAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::SoftmaxAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_SOFTMAX_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief Description of a Softmax operation on input Tensor along a specified axis.
@@ -68,7 +80,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<SoftmaxAttr, std::int32_t>;
+    using Attributes_ = StaticAttributes<SoftmaxAttr, GENERATE_LIST_ATTR_TYPE(LIST_SOFTMAX_ATTR)>;
     template <SoftmaxAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -143,7 +155,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::SoftmaxAttr>::data;
 	}
 };
@@ -159,4 +171,6 @@ std::shared_ptr<Node> Softmax(std::int32_t axis, const std::string& name = "");
 
 } // namespace Aidge
 
+#undef LIST_SOFTMAX_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_SOFTMAX_H_ */
diff --git a/include/aidge/operator/Split.hpp b/include/aidge/operator/Split.hpp
index 8b6acb06023f5f71cbb71b42281f21bda19caaed..038879f05dfc57f6451d0c490ec52e8283a1b93f 100644
--- a/include/aidge/operator/Split.hpp
+++ b/include/aidge/operator/Split.hpp
@@ -44,36 +44,41 @@ public:
      */
     void forward() override;
 };
+} // naemspace Aidge
 
+#define LIST_SPLIT_ATTR(X) \
+    X(Axis, "axis", std::int8_t), \
+    X(Split, "split", std::vector<DimSize_t>)
+
+namespace Aidge {
 /**
  * @enum SplitAttr
  * @brief Enumeration of Split operator attributes.
+ *
+ * - Axis: Axis along which to split the input tensor.
+ *   - The specified axis determines the direction of splitting.
+ * - Split: Sizes of each output tensor after splitting.
+ *   - If specified, the sum of the split sizes must match the size of the input
+ *     tensor along the specified axis.
  */
 enum class SplitAttr {
-    /**
-     * @brief Axis along which to split the input tensor.
-     *
-     * The specified axis determines the direction of splitting.
-     */
-    Axis,
-
-    /**
-     * @brief Sizes of each output tensor after splitting.
-     *
-     * If specified, the sum of the split sizes must match the size of the input
-     * tensor along the specified axis.
-     */
-    Split
+    GENERATE_LIST_ATTR_ENUM(LIST_SPLIT_ATTR)
 };
 } // namespace Aidge
 
 namespace {
-    /**
-     * @brief EnumStrings specialization for SplitAttr.
-     */
-    template <>
-    const char* const EnumStrings<Aidge::SplitAttr>::data[] = {"axis", "split"};
-    }
+/**
+ * @brief EnumStrings specialization for SplitAttr.
+ */
+template <>
+struct EnumStrings<Aidge::SplitAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::SplitAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_SPLIT_ATTR)
+};
+}
+
 
 namespace Aidge {
 /**
@@ -109,7 +114,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<SplitAttr, std::int8_t, std::vector<DimSize_t>>;
+    using Attributes_ = StaticAttributes<SplitAttr,GENERATE_LIST_ATTR_TYPE(LIST_SPLIT_ATTR)>;
     template <SplitAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -188,7 +193,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::SplitAttr>::data;
 	}
 };
@@ -209,5 +214,6 @@ std::shared_ptr<Node> Split(DimSize_t nbOutput,
 
 }  // namespace Aidge
 
+#undef LIST_SPLIT_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_SPLIT_H_ */
diff --git a/include/aidge/operator/Squeeze.hpp b/include/aidge/operator/Squeeze.hpp
index 69fa9d493a321199ea2fddd61c7b769a668c6f42..987f1e6af1452d9513cd855e63a8f8504721e25a 100644
--- a/include/aidge/operator/Squeeze.hpp
+++ b/include/aidge/operator/Squeeze.hpp
@@ -40,19 +40,34 @@ public:
       : OperatorImpl(op, backend) {}
   void forward() override;
 };
+} // namespace Aidge
+
+#define LIST_SQUEEZE_ATTR(X) \
+    X(Axes, "axes", std::vector<std::int8_t>)
 
+namespace Aidge {
+/**
+ * @enum SqueezeAttr
+ * @brief Enumeration of Squeeze operator attributes.
+ *
+ * - Axes: axes to squeeze, if left empty all 1 sized
+ * dimensions will be removed.
+ */
 enum class SqueezeAttr {
-  /**
-   * @brief axes to squeeze, if left empty all 1 sized
-   * dimensions will be removed.
-   */
-  Axes
+    GENERATE_LIST_ATTR_ENUM(LIST_SQUEEZE_ATTR)
 };
 } // namespace Aidge
+
 namespace {
-  template <>
-  const char *const EnumStrings<Aidge::SqueezeAttr>::data[] = {"axes"};
+template <>
+struct EnumStrings<Aidge::SqueezeAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::SqueezeAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_SQUEEZE_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief This operator has as purpose to remove dummy dimensions around given
@@ -152,7 +167,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::SqueezeAttr>::data;
 	}
 };
@@ -165,4 +180,6 @@ inline std::shared_ptr<Node> Squeeze(const std::vector<int8_t> axes = {},
 }
 } // namespace Aidge
 
+#undef LIST_SQUEEZE_ATTR
+
 #endif // AIDGE_CORE_OPERATOR_SQUEEZE_H_
diff --git a/include/aidge/operator/Stack.hpp b/include/aidge/operator/Stack.hpp
index d22b2f2dde9d9254ca7dd0f81f1a9f7bd35d9f6b..84341375649e6d8d4948283971e86042cc003fd4 100644
--- a/include/aidge/operator/Stack.hpp
+++ b/include/aidge/operator/Stack.hpp
@@ -96,19 +96,34 @@ public:
     void backward() override;
 };
 
+#define LIST_STACK_ATTR(X)  \
+    X(ForwardStep, "forward_step", std::uint32_t), \
+    X(BackwardStep, "backward_step", std::uint32_t), \
+    X(MaxElements, "max_elements", std::uint32_t)
+
+/**
+ * @enum StackAttr
+ * @brief Attributes for the Stack operation.
+ *
+ * - ForwardStep: Tracks the current step in the forward pass.
+ * - BackwardStep: Tracks the current step in the backward pass.
+ * - MaxElements: Maximum number of elements that can be stacked.
+ */
 enum class StackAttr {
-    ForwardStep,   // Tracks the current step in the forward pass.
-    BackwardStep,  // Tracks the current step in the forward pass.
-    MaxElements    // Maximum number of elements that can be stacked.
+    GENERATE_LIST_ATTR_ENUM(LIST_STACK_ATTR)
 };
 }  // namespace Aidge
+
 namespace {
-    /**
-     * @brief String representations of the Stack operator's attributes.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::StackAttr>::data[] = {"forward_step", "backward_step", "max_elements"};
+template <>
+struct EnumStrings<Aidge::StackAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::StackAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_STACK_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @class StackOp
@@ -129,7 +144,9 @@ namespace Aidge {
 class StackOp : public OperatorTensor,
     public Registrable<StackOp, std::string, std::function<std::unique_ptr<OperatorImpl>(const StackOp&)>> {
 private:
-    using Attributes_ = StaticAttributes<StackAttr, std::uint32_t, std::uint32_t, std::uint32_t>;
+    using Attributes_ = StaticAttributes<StackAttr,
+            GENERATE_LIST_ATTR_TYPE(LIST_STACK_ATTR)
+        >;
     template <StackAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -245,7 +262,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::StackAttr>::data;
 	}
 };
@@ -259,4 +276,6 @@ public:
 std::shared_ptr<Node> Stack(std::uint32_t maxElements = 0, const std::string& name = "");
 }  // namespace Aidge
 
+#undef LIST_STACK_ATTR
+
 #endif /* AIDGE_CORE_OPERATOR_STACK_H_ */
diff --git a/include/aidge/operator/Transpose.hpp b/include/aidge/operator/Transpose.hpp
index 2619c5ea5d41407100b66f909d6f64176027f74c..25d8d92f67901dbeb7cf0610a0f818cdbf60b0bd 100644
--- a/include/aidge/operator/Transpose.hpp
+++ b/include/aidge/operator/Transpose.hpp
@@ -46,28 +46,11 @@ public:
      */
     void forward() override;
 };
-
-/**
- * @enum TransposeAttr
- * @brief Enumeration of attributes specific to the Transpose operator.
- */
-enum class TransposeAttr {
-    /**
-     * @brief Order of the output dimensions relative to the input dimensions.
-     *
-     * If this attribute is empty, the dimensions of the input tensor will
-     * be reversed.
-     */
-    OutputDimsOrder
-};
 } // namespace Aidge
-namespace {
-    /**
-     * @brief EnumStrings specialization for TransposeAttr.
-     */
-    template <>
-    const char *const EnumStrings<Aidge::TransposeAttr>::data[] = {"output_dims_order"};
-    }
+
+#define LIST_TRANSPOSE_ATTR(X) \
+    X(OutputDimsOrder, "output_dims_order", std::vector<DimSize_t>)
+
 namespace Aidge {
 /**
  * @brief Describes the operation of transposing the axes of a given tensor.
@@ -84,17 +67,30 @@ namespace Aidge {
  * @see Registrable
  */
 class Transpose_Op : public OperatorTensor,
-                public Registrable<Transpose_Op, std::string, std::function<std::shared_ptr<OperatorImpl>(const Transpose_Op&)>> {
-
+                public Registrable<Transpose_Op,
+                                   std::string,
+                                   std::function<std::shared_ptr<OperatorImpl>(const Transpose_Op&)>> {
 public:
     /**
      * @brief Static type string for the Transpose operator.
      */
     static const std::string Type;
 
+    /**
+     * @enum Attr
+     * @brief Enumeration of attributes specific to the Transpose operator.
+     *
+     * - OutputDimsOrder:  Order of the output dimensions relative to the input dimensions.
+     * If this attribute is empty, the dimensions of the input tensor will
+     * be reversed.
+     */
+    enum class Attr {
+        GENERATE_LIST_ATTR_ENUM(LIST_TRANSPOSE_ATTR)
+    };
+
 private:
-    using Attributes_ = StaticAttributes<TransposeAttr, std::vector<DimSize_t>>;
-    template <TransposeAttr e> using attr = typename Attributes_::template attr<e>;
+    using Attributes_ = StaticAttributes<Attr, GENERATE_LIST_ATTR_TYPE(LIST_TRANSPOSE_ATTR)>;
+    template <Attr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
 public:
@@ -156,7 +152,7 @@ public:
      * If left empty, axes will be reversed.
      */
     inline std::vector<DimSize_t>& outputDimsOrder() const noexcept {
-        return mAttributes->getAttr<TransposeAttr::OutputDimsOrder>();
+        return mAttributes->getAttr<Attr::OutputDimsOrder>();
     }
 
     /**
@@ -179,9 +175,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
-		return EnumStrings<Aidge::TransposeAttr>::data;
-	}
+	static constexpr const char* const* attributesName();
 };
 
 /**
@@ -196,5 +190,20 @@ std::shared_ptr<Node> Transpose(const std::vector<DimSize_t> &outputDimsOrder =
 
 }  // namespace Aidge
 
+namespace {
+template <>
+struct EnumStrings<Aidge::Transpose_Op::Attr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::Transpose_Op::Attr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_TRANSPOSE_ATTR)
+};
+}
+
+constexpr const char* const* Aidge::Transpose_Op::attributesName() {
+    return EnumStrings<Aidge::Transpose_Op::Attr>::data;
+}
+
+#undef LIST_TRANSPOSE_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_TRANSPOSE_H_ */
diff --git a/include/aidge/operator/Unfold.hpp b/include/aidge/operator/Unfold.hpp
index d220807d6cd4ea2c57c152c9e8351bc48211d06e..fe85f9d5e999ab2e6b6a0ae65f3d8ef43cdea0b3 100644
--- a/include/aidge/operator/Unfold.hpp
+++ b/include/aidge/operator/Unfold.hpp
@@ -22,9 +22,7 @@
 #include "aidge/data/Tensor.hpp"
 #include "aidge/graph/Node.hpp"
 #include "aidge/operator/OperatorTensor.hpp"
-#include "aidge/operator/Producer.hpp"
 #include "aidge/utils/ArrayHelpers.hpp"
-#include "aidge/utils/ErrorHandling.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/StaticAttributes.hpp"
 #include "aidge/utils/Types.h"
@@ -50,39 +48,37 @@ public:
      */
     void forward() override;
 };
+} //namespace Aidge
 
+#define LIST_UNFOLD_ATTR(X)  \
+    X(StrideDims, "stride_dims", sizeArr_t<DIM>),  \
+    X(DilationDims, "dilation_dims", sizeArr_t<DIM>),  \
+    X(KernelDims, "kernel_dims", sizeArr_t<DIM>)
+
+namespace Aidge {
 /**
  * @enum UnfoldAttr
- * @brief Enumeration of attributes specific to the Unfold operator.
+ * @brief Enumeration for the attributes of the Unfold operation.
+ *
+ * - StrideDims: Step sizes in each dimension during the unfold operation.
+ * - DilationDims: Spacing between elements in the kernel during the unfold.
+ * - KernelDims: Size of the kernel or filter applied during the unfold.
  */
 enum class UnfoldAttr {
-    /**
-     * @brief Stride dimensions for the unfolding operation.
-     */
-    StrideDims,
-
-    /**
-     * @brief Dilation dimensions for the unfolding operation.
-     */
-    DilationDims,
-
-    /**
-     * @brief Kernel dimensions for the unfolding operation.
-     */
-    KernelDims
+    GENERATE_LIST_ATTR_ENUM(LIST_UNFOLD_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-    /**
-     * @brief EnumStrings specialization for UnfoldAttr.
-     */
-    template <>
-    const char* const EnumStrings<Aidge::UnfoldAttr>::data[] = {
-        "stride_dims",
-        "dilation_dims",
-        "kernel_dims"
-    };
+template <>
+struct EnumStrings<Aidge::UnfoldAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::UnfoldAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_UNFOLD_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief Describes the operation of unfolding a tensor into sliding blocks.
@@ -109,10 +105,7 @@ public:
     static const std::string Type;
 
 private:
-    using Attributes_ = StaticAttributes<UnfoldAttr,
-                                         std::array<DimSize_t, DIM>,
-                                         std::array<DimSize_t, DIM>,
-                                         std::array<DimSize_t, DIM>>;
+    using Attributes_ = StaticAttributes<UnfoldAttr, GENERATE_LIST_ATTR_TYPE(LIST_UNFOLD_ATTR)>;
     template <UnfoldAttr e> using attr = typename Attributes_::template attr<e>;
     const std::shared_ptr<Attributes_> mAttributes;
 
@@ -216,7 +209,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::UnfoldAttr>::data;
 	}
 };
@@ -249,5 +242,6 @@ inline std::shared_ptr<Node> Unfold( DimSize_t const (&kernelDims)[DIM],
 
 extern template class Aidge::Unfold_Op<2>;
 
+#undef LIST_UNFOLD_ATTR
 
 #endif /* AIDGE_CORE_OPERATOR_UNFOLD_H_ */
diff --git a/include/aidge/operator/Unsqueeze.hpp b/include/aidge/operator/Unsqueeze.hpp
index a78a986724d4b5ca06f611b82e057d13183c5015..5975ff0578ee89a50e1871b71f77846cb63c9d4d 100644
--- a/include/aidge/operator/Unsqueeze.hpp
+++ b/include/aidge/operator/Unsqueeze.hpp
@@ -37,21 +37,35 @@ public:
       : OperatorImpl(op, backend) {}
   void forward() override;
 };
+}  // namespace Aidge
 
+#define LIST_UNSQUEEZE_ATTR(X)  \
+    X(Axes, "axes", std::vector<std::int8_t>)
+
+namespace Aidge {
+/**
+ * @enum UnsqueezeAttr
+ * @brief Attributes for the Unsqueeze operation.
+ *
+ * - Axes: A vector of axes to unsqueeze.
+ *   - Values must be within the range [ -a ; a-1 ],
+ *     where `a = input_tensor.nbDim() + dims_to_unsqueeze.size()`.
+ */
 enum class UnsqueezeAttr {
-  /**
-   * @brief vector of axes to unsqueeze.
-   * values must be comprised within
-   * [ -a ; a-1 ]
-   * with a = input_tensor.nbDim() + dims_to_unsqueeze.size()
-   */
-  Axes
+    GENERATE_LIST_ATTR_ENUM(LIST_UNSQUEEZE_ATTR)
 };
-} // namespace Aidge
+}  // namespace Aidge
+
 namespace {
-  template <>
-  const char *const EnumStrings<Aidge::UnsqueezeAttr>::data[] = {"axes"};
+template <>
+struct EnumStrings<Aidge::UnsqueezeAttr> {
+    static const char* const data[];
+};
+constexpr const char* const EnumStrings<Aidge::UnsqueezeAttr>::data[] = {
+    GENERATE_LIST_ATTR_STR(LIST_UNSQUEEZE_ATTR)
+};
 }
+
 namespace Aidge {
 /**
  * @brief This operator has as purpose to add a dummy dimension around given
@@ -72,7 +86,7 @@ public:
       Type; // name of the type of the operation (Here "Unsqueeze")
 
 private:
-  using Attributes_ = StaticAttributes<UnsqueezeAttr, std::vector<int8_t>>;
+  using Attributes_ = StaticAttributes<UnsqueezeAttr, GENERATE_LIST_ATTR_TYPE(LIST_UNSQUEEZE_ATTR)>;
   template <UnsqueezeAttr e>
   using attr = typename Attributes_::template attr<e>;
   const std::shared_ptr<Attributes_> mAttributes;
@@ -150,7 +164,7 @@ public:
 	 * @brief Retrieves the names of the attributes for the operator.
 	 * @return A vector containing the attributes name.
 	 */
-	static const char* const* attributesName(){
+	static constexpr const char* const* attributesName(){
 		return EnumStrings<Aidge::UnsqueezeAttr>::data;
 	}
 };
@@ -163,4 +177,6 @@ inline std::shared_ptr<Node> Unsqueeze(const std::vector<int8_t> &axes = {},
 }
 } // namespace Aidge
 
+#undef LIST_UNSQUEEZE_ATTR
+
 #endif // AIDGE_CORE_OPERATOR_UNSQUEEZE_H_
diff --git a/include/aidge/utils/Types.h b/include/aidge/utils/Types.h
index b601df1cb8f8fa81cd2339e7eb393f7297e63499..6cbf375578b5e42b6f3db03ff990a2c791ef49c7 100644
--- a/include/aidge/utils/Types.h
+++ b/include/aidge/utils/Types.h
@@ -60,6 +60,8 @@ constexpr IOIndex_t gk_IOMaxIndex = std::numeric_limits<IOIndex_t>::max() - 1;
 // using IOIndex_t = std::uint16_t;
 // constexpr IOIndex_t gk_IOMaxNb = std::numeric_limits<IOIndex_t>::max();
 
+// type used by StaticAttribute MACROs
+template <DimSize_t DIM> using sizeArr_t = std::array<DimSize_t, DIM>;
 
 } // namespace Aidge
 
diff --git a/src/operator/Conv.cpp b/src/operator/Conv.cpp
index 2077cab52f613780e77bba80efacb41d06a7f3cf..8e23e8b3c6ce5fa3ac179bb058e7e04d5905b6b2 100644
--- a/src/operator/Conv.cpp
+++ b/src/operator/Conv.cpp
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "aidge/data/Tensor.hpp"
+#include "aidge/operator/Producer.hpp"
 #include "aidge/utils/ErrorHandling.hpp"
 #include "aidge/utils/Registrar.hpp"
 #include "aidge/utils/Types.h"
@@ -40,7 +41,7 @@ Aidge::Conv_Op<DIM>::Conv_Op(const Aidge::Conv_Op<DIM>& op)
 
 template <Aidge::DimIdx_t DIM>
 bool Aidge::Conv_Op<DIM>::forwardDims(bool /*allowDataDependency*/) {
-    if (!inputsAssociated()) 
+    if (!inputsAssociated())
         return false;
     // first check weight since it defines inChannels and outChannels
     if(getInput(0)->dataFormat() == Aidge::DataFormat::NHWC){
@@ -65,31 +66,31 @@ bool Aidge::Conv_Op<DIM>::forwardDims(bool /*allowDataDependency*/) {
     const std::array<DimSize_t, DIM + 2> inputDims(getInput(0)->template dims<DIM+2>());
     std::array<DimSize_t, DIM + 2> outputDims{};
 
-    
+
     unsigned int in_dims_index = (getInput(0)->dataFormat() == Aidge::DataFormat::NHWC) ? 1 : 2;
     unsigned int out_dims_index = (getOutput(0)->dataFormat() == Aidge::DataFormat::NHWC) ? 1 : 2;
 
-    for (std::size_t dim = 0; dim < mAttributes->template getAttr<ConvAttr::KernelDims>().size(); ++dim) {
-        const DimSize_t kernelExtent = mAttributes->template getAttr<ConvAttr::DilationDims>()[dim] *
-                                    (mAttributes->template getAttr<ConvAttr::KernelDims>()[dim] - 1) +
+    for (std::size_t dim = 0; dim < mAttributes->template getAttr<Attr::KernelDims>().size(); ++dim) {
+        const DimSize_t kernelExtent = mAttributes->template getAttr<Attr::DilationDims>()[dim] *
+                                    (mAttributes->template getAttr<Attr::KernelDims>()[dim] - 1) +
                                     1;
-        
+
         outputDims[dim + out_dims_index] = 1 + static_cast<DimSize_t>(
             floor(static_cast<float>(inputDims[dim + in_dims_index] - kernelExtent) /
-                static_cast<float>(mAttributes->template getAttr<ConvAttr::StrideDims>()[dim]))
+                static_cast<float>(mAttributes->template getAttr<Attr::StrideDims>()[dim]))
         );
     }
 
-    if(getOutput(0)->dataFormat() == Aidge::DataFormat::NHWC) 
+    if(getOutput(0)->dataFormat() == Aidge::DataFormat::NHWC)
         outputDims[DIM+1] = outChannels();
-    else 
+    else
         outputDims[1] = outChannels();
 
     outputDims[0] = inputDims[0];
     mOutputs[0]->resize(outputDims);
     return true;
-    
-    
+
+
 }
 
 template <Aidge::DimIdx_t DIM>
@@ -122,18 +123,18 @@ Aidge::Conv_Op<DIM>::computeReceptiveField(
         std::vector<DimSize_t> inputDims{outputDims[0], getInput(0)->dims()[1]};
         for (DimIdx_t i = 0; i < DIM; ++i) {
             inputDims.push_back((outputDims[2+static_cast<std::size_t>(i)] - 1)
-                        * mAttributes->template getAttr<ConvAttr::StrideDims>()[static_cast<std::size_t>(i)]
+                        * mAttributes->template getAttr<Attr::StrideDims>()[static_cast<std::size_t>(i)]
                         + 1
-                        + (mAttributes->template getAttr<ConvAttr::KernelDims>()[static_cast<std::size_t>(i)] - 1)
-                        * mAttributes->template getAttr<ConvAttr::DilationDims>()[static_cast<std::size_t>(i)]);
-            inputIdxDims[2+i] *= mAttributes->template getAttr<ConvAttr::StrideDims>()[static_cast<std::size_t>(i)];
+                        + (mAttributes->template getAttr<Attr::KernelDims>()[static_cast<std::size_t>(i)] - 1)
+                        * mAttributes->template getAttr<Attr::DilationDims>()[static_cast<std::size_t>(i)]);
+            inputIdxDims[2+i] *= mAttributes->template getAttr<Attr::StrideDims>()[static_cast<std::size_t>(i)];
         }
 
         // Weight
         // same output value, every input channel is used
         std::vector<DimSize_t> weightDims{outputDims[1], getInput(0)->dims()[1]};
         for (std::size_t i = 0; i < DIM; ++i) {
-            weightDims.push_back(mAttributes->template getAttr<ConvAttr::KernelDims>()[i]);
+            weightDims.push_back(mAttributes->template getAttr<Attr::KernelDims>()[i]);
         }
         std::vector<DimSize_t> weightIdxDims = std::vector<DimSize_t>(DIM+2, 0);
         weightIdxDims[0] = firstEltDims[1];
@@ -173,6 +174,28 @@ void Aidge::Conv_Op<DIM>::setBackend(const std::string &name, Aidge::DeviceIdx_t
     }
 }
 
+template <Aidge::DimIdx_t DIM>
+Aidge::DimSize_t Aidge::Conv_Op<DIM>::inChannels() const {
+    if (!getInput(1)) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "Convolution operator has no weight Tensor associated so no specific number of input channel imposed.");
+    }
+
+    // check format
+    if(getInput(1)->dataFormat()==Aidge::DataFormat::NHWC)
+        return getInput(1)->template dims<DIM+2>()[DIM+1];
+    // default format is NCHW
+    return getInput(1)->template dims<DIM+2>()[1];
+}
+
+template <Aidge::DimIdx_t DIM>
+Aidge::DimSize_t Aidge::Conv_Op<DIM>::outChannels() const {
+    if (!getInput(1)) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "Convolution operator has no weight Tensor associated so no specific number of output channel imposed.");
+    }
+    // first weight dimension for both NCHW (Cout,Cin,H,W) and NHWC (Cout,H,W,Cin) data format
+    return getInput(1)->template dims<DIM+2>()[0];
+}
+
 template <Aidge::DimIdx_t DIM>
 std::set<std::string> Aidge::Conv_Op<DIM>::getAvailableBackends() const {
     return Registrar<Conv_Op<DIM>>::getKeys();
@@ -203,3 +226,20 @@ std::shared_ptr<Aidge::Node> Aidge::Conv(Aidge::DimSize_t inChannels,
 
 template std::shared_ptr<Aidge::Node> Aidge::Conv<1>(Aidge::DimSize_t, Aidge::DimSize_t, const std::array<Aidge::DimSize_t, 1>&, const std::string&, const std::array<Aidge::DimSize_t, 1>&, const std::array<Aidge::DimSize_t, 1>&, bool);
 template std::shared_ptr<Aidge::Node> Aidge::Conv<2>(Aidge::DimSize_t, Aidge::DimSize_t, const std::array<Aidge::DimSize_t, 2>&, const std::string&, const std::array<Aidge::DimSize_t, 2>&, const std::array<Aidge::DimSize_t, 2>&, bool);
+
+template <Aidge::DimSize_t DIM>
+std::shared_ptr<Aidge::Node> Aidge::Conv(
+    Aidge::DimSize_t inChannels,
+    Aidge::DimSize_t outChannels,
+    Aidge::DimSize_t const (&kernelDims)[DIM],
+    const std::string& name,
+    const std::array<Aidge::DimSize_t, DIM> &strideDims,
+    const std::array<Aidge::DimSize_t, DIM> &dilationDims,
+    bool noBias)
+{
+    static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Conv, not supported");
+    return Conv(inChannels, outChannels, to_array(kernelDims), name, strideDims, dilationDims, noBias);
+}
+
+template std::shared_ptr<Aidge::Node> Aidge::Conv<1>(Aidge::DimSize_t, Aidge::DimSize_t, Aidge::DimSize_t const (&)[1], const std::string&, const std::array<Aidge::DimSize_t, 1>&, const std::array<Aidge::DimSize_t, 1>&, bool);
+template std::shared_ptr<Aidge::Node> Aidge::Conv<2>(Aidge::DimSize_t, Aidge::DimSize_t, Aidge::DimSize_t const (&)[2], const std::string&, const std::array<Aidge::DimSize_t, 2>&, const std::array<Aidge::DimSize_t, 2>&, bool);
\ No newline at end of file
diff --git a/src/operator/Gather.cpp b/src/operator/Gather.cpp
index e0990437a06d5b9fb72cf1909d78f6094120bf80..10e20046f0565d098275141e90e920ce78725e0f 100644
--- a/src/operator/Gather.cpp
+++ b/src/operator/Gather.cpp
@@ -28,9 +28,9 @@ Aidge::Gather_Op::Gather_Op(std::int8_t axis,
               const std::vector<Aidge::DimSize_t>& gatheredShape)
     : OperatorTensor(Type, {InputCategory::Data, InputCategory::OptionalData}, 1),
     mAttributes(std::make_shared<Attributes_>(
-        attr<GatherAttr::Axis>(axis),
-        attr<GatherAttr::Indices>(indices),
-        attr<GatherAttr::GatheredShape>(gatheredShape)))
+        attr<Attr::Axis>(axis),
+        attr<Attr::Indices>(indices),
+        attr<Attr::GatheredShape>(gatheredShape)))
 {
     mImpl = std::make_shared<Gather_OpImpl>(*this);
 }
diff --git a/src/operator/Heaviside.cpp b/src/operator/Heaviside.cpp
index 9ecb3b436d8312ef479d6bc0592cfe372235fa25..6555a530bd02edf6f1823469297d289fb4b57b87 100644
--- a/src/operator/Heaviside.cpp
+++ b/src/operator/Heaviside.cpp
@@ -30,7 +30,7 @@ const std::string Heaviside_Op::Type = "Heaviside";
 Heaviside_Op::Heaviside_Op(float value)
     : OperatorTensor(Type, {InputCategory::Data}, 1),
       mAttributes(
-          std::make_shared<Attributes_>(attr<HeavisideAttr::Value>(value))) {}
+          std::make_shared<Attributes_>(attr<Attr::Value>(value))) {}
 
 Heaviside_Op::Heaviside_Op(const Heaviside_Op &op)
     : OperatorTensor(op), mAttributes(op.mAttributes) {
diff --git a/src/operator/LRN.cpp b/src/operator/LRN.cpp
index c5ce243bd6a48ae4b1ce8461924b498c804b53e6..5b7d663e78cf92047e3ed47212f2a27d42a8de49 100644
--- a/src/operator/LRN.cpp
+++ b/src/operator/LRN.cpp
@@ -23,10 +23,10 @@ const std::string Aidge::LRN_Op::Type = "LRN";
 Aidge::LRN_Op::LRN_Op(std::int32_t size)
     : OperatorTensor(Type, {InputCategory::Data}, 1),
     mAttributes(std::make_shared<Attributes_>(
-        attr<LRNAttr::Alpha>(0.0001),
-        attr<LRNAttr::Beta>(0.75),
-        attr<LRNAttr::Bias>(1.0),
-        attr<LRNAttr::Size>(size)))
+        attr<Attr::Alpha>(0.0001),
+        attr<Attr::Beta>(0.75),
+        attr<Attr::Bias>(1.0),
+        attr<Attr::Size>(size)))
 {}
 
 Aidge::LRN_Op::LRN_Op(const Aidge::LRN_Op& op)
diff --git a/src/operator/Memorize.cpp b/src/operator/Memorize.cpp
index c4f0bc4bf7267d24264652d5ed6b0d50935e1aa4..76d3ddd22e113f087f5afc0ebb358edce0b0fc32 100644
--- a/src/operator/Memorize.cpp
+++ b/src/operator/Memorize.cpp
@@ -78,9 +78,9 @@ const std::string Aidge::Memorize_Op::Type = "Memorize";
 Aidge::Memorize_Op::Memorize_Op(const std::uint32_t endStep)
     : OperatorTensor(Type, {InputCategory::Data, InputCategory::Data}, 2),
         mAttributes(std::make_shared<Attributes_>(
-                    attr<MemorizeAttr::ScheduleStep>(0),
-                    attr<MemorizeAttr::ForwardStep>(0),
-                    attr<MemorizeAttr::EndStep>(endStep)))
+                    attr<Attr::ScheduleStep>(0),
+                    attr<Attr::ForwardStep>(0),
+                    attr<Attr::EndStep>(endStep)))
 {
     // The input idx 0 is a back edge for Memorize where inputs are (back, init)
     setBackEdges({0});
@@ -106,8 +106,8 @@ std::shared_ptr<Aidge::Operator> Aidge::Memorize_Op::clone() const {
 
 void Aidge::Memorize_Op::updateConsummerProducer() {
     Operator::updateConsummerProducer();
-    ++mAttributes->template getAttr<MemorizeAttr::ScheduleStep>();
-    mAttributes->template getAttr<MemorizeAttr::ForwardStep>() = 0;
+    ++scheduleStep();
+    forwardStep() = 0;
 }
 
 bool Aidge::Memorize_Op::forwardDims(bool /*allowDataDependency*/) {
@@ -151,8 +151,8 @@ void Aidge::Memorize_Op::setBackend(const std::string& name, Aidge::DeviceIdx_t
 
 void Aidge::Memorize_Op::forward() {
     OperatorTensor::forward();
-    ++mAttributes->template getAttr<MemorizeAttr::ForwardStep>();
-    mAttributes->template getAttr<MemorizeAttr::ScheduleStep>() = 0;
+    ++forwardStep();
+    scheduleStep() = 0;
 }
 
 std::set<std::string> Aidge::Memorize_Op::getAvailableBackends() const {
diff --git a/src/operator/Producer.cpp b/src/operator/Producer.cpp
index 9af4586886fc98c50862672392d3b704e6bc1d0c..0beaf91b3a31ee9347a91ae4b77287ac0abcdc20 100644
--- a/src/operator/Producer.cpp
+++ b/src/operator/Producer.cpp
@@ -32,7 +32,7 @@ Aidge::Producer_Op::Producer_Op(
             bool constant)
     : OperatorTensor(Type, {}, 1),
         mAttributes(std::make_shared<Attributes_>(
-        attr<ProdAttr::Constant>(constant)))
+        attr<ProducerAttr::Constant>(constant)))
 {
     mOutputs[0]->resize(dims);
     mImpl = std::make_shared<OperatorImpl>(*this);
@@ -41,7 +41,7 @@ Aidge::Producer_Op::Producer_Op(
 Aidge::Producer_Op::Producer_Op(const std::shared_ptr<Aidge::Tensor> tensor, bool constant)
     : OperatorTensor(Type, {}, 1),
       mAttributes(std::make_shared<Attributes_>(
-        attr<ProdAttr::Constant>(constant)))
+        attr<ProducerAttr::Constant>(constant)))
 {
     mOutputs[0] = tensor; // copy the pointer of the Tensor
     if (mOutputs[0] && mOutputs[0]->hasImpl() && Registrar<Producer_Op>::exists({mOutputs[0]->getImpl()->backend()})){
@@ -100,7 +100,7 @@ void Aidge::Producer_Op::forward() {
 }
 
 void Aidge::Producer_Op::setOutput(const Aidge::IOIndex_t outputIdx, const std::shared_ptr<Aidge::Data>& data) const {
-    if (mAttributes->template getAttr<ProdAttr::Constant>()) {
+    if (mAttributes->template getAttr<ProducerAttr::Constant>()) {
         AIDGE_THROW_OR_ABORT(std::runtime_error, "Producer is constant, cannot update output.");
     }
     OperatorTensor::setOutput(outputIdx, data);
diff --git a/src/operator/ReduceMean.cpp b/src/operator/ReduceMean.cpp
index 7935edb050824af92a8f130f975aa09e41ca875f..dfaa75a4883ce2c9dcc77f89dc9f970c3f1ed2cd 100644
--- a/src/operator/ReduceMean.cpp
+++ b/src/operator/ReduceMean.cpp
@@ -30,9 +30,9 @@ const std::string Aidge::ReduceMean_Op::Type = "ReduceMean";
 Aidge::ReduceMean_Op::ReduceMean_Op(const std::vector<std::int32_t>& axes, bool keep_dims, bool noop_with_empty_axes)
     : OperatorTensor(Type, {InputCategory::Data}, 1),
         mAttributes(std::make_shared<Attributes_>(
-        attr<ReduceMeanAttr::Axes>(axes),
-        attr<ReduceMeanAttr::KeepDims>(keep_dims),
-        attr<ReduceMeanAttr::NoopWithEmptyAxes>(noop_with_empty_axes)))
+        attr<Attr::Axes>(axes),
+        attr<Attr::KeepDims>(keep_dims),
+        attr<Attr::NoopWithEmptyAxes>(noop_with_empty_axes)))
 {}
 
 Aidge::ReduceMean_Op::ReduceMean_Op(const Aidge::ReduceMean_Op& op)
@@ -53,32 +53,32 @@ std::shared_ptr<Aidge::Operator> Aidge::ReduceMean_Op::clone() const {
 bool Aidge::ReduceMean_Op::forwardDims(bool /*allowDataDependency*/) {
     if (inputsAssociated()) {
         // make Axes attribute positive
-        std::vector<std::int32_t>& axes = mAttributes->template getAttr<ReduceMeanAttr::Axes>();
-        std::for_each(axes.begin(), axes.end(), [&] (std::int32_t& val) {
+        std::vector<std::int32_t>& reduced_axes = axes();
+        std::for_each(reduced_axes.begin(), reduced_axes.end(), [&] (std::int32_t& val) {
             if (val < 0)
                 val+=static_cast<std::int32_t>(getInput(0)->nbDims());
         });
-        std::sort(axes.begin(), axes.end());
+        std::sort(reduced_axes.begin(), reduced_axes.end());
 
         // build output dimensions
         std::vector<DimSize_t> outDims = getInput(0)->dims();
 
-        if (axes.empty())
+        if (reduced_axes.empty())
         {
-            if(mAttributes->template getAttr<ReduceMeanAttr::NoopWithEmptyAxes>()) {
+            if(noopWithEmptyAxes()) {
                 mOutputs[0]->resize(outDims);
                 return true;
             }
             // if no axes are provided and NoopWithEmptyAxes is false, reduce on all axes
-            axes.resize(getInput(0)->nbDims());
-            std::iota(axes.begin(), axes.end(), 0);
+            reduced_axes.resize(getInput(0)->nbDims());
+            std::iota(reduced_axes.begin(), reduced_axes.end(), 0);
         }
 
-        if (mAttributes->template getAttr<ReduceMeanAttr::KeepDims>()) {
-            std::for_each(axes.cbegin(), axes.cend(), [&outDims] (const std::int32_t& val) { outDims[val] = 1; });
+        if (keepDims()) {
+            std::for_each(reduced_axes.cbegin(), reduced_axes.cend(), [&outDims] (const std::int32_t& val) { outDims[val] = 1; });
         }
         else {
-            for (auto it = axes.crbegin(); it != axes.crend(); ++it)
+            for (auto it = reduced_axes.crbegin(); it != reduced_axes.crend(); ++it)
                 outDims.erase(outDims.begin() + static_cast<std::size_t>(*it));
         }
 
diff --git a/src/operator/ReduceSum.cpp b/src/operator/ReduceSum.cpp
index 0786f53c6b761e5cd9020352a2ecb92469a609d7..73b6722e15ebc7a32cbb502e83d5779558c1cac7 100644
--- a/src/operator/ReduceSum.cpp
+++ b/src/operator/ReduceSum.cpp
@@ -30,32 +30,32 @@ const std::string Aidge::ReduceSum_Op::Type = "ReduceSum";
 bool Aidge::ReduceSum_Op::forwardDims(bool /*allowDataDependency*/) {
     if (inputsAssociated()) {
         // make Axes attribute positive
-        std::vector<std::int32_t>& axes = mAttributes->template getAttr<ReduceSumAttr::Axes>();
-        std::for_each(axes.begin(), axes.end(), [&] (std::int32_t& val) {
+        std::vector<std::int32_t>& reduced_axes = axes();
+        std::for_each(reduced_axes.begin(), reduced_axes.end(), [&] (std::int32_t& val) {
             if (val < 0)
                 val+=static_cast<std::int32_t>(getInput(0)->nbDims());
         });
-        std::sort(axes.begin(), axes.end());
+        std::sort(reduced_axes.begin(), reduced_axes.end());
 
         // build output dimensions
         std::vector<DimSize_t> outDims = getInput(0)->dims();
 
-        if (axes.empty())
+        if (reduced_axes.empty())
         {
-            if(mAttributes->template getAttr<ReduceSumAttr::NoopWithEmptyAxes>()) {
+            if(noopWithEmptyAxes()) {
                 mOutputs[0]->resize(outDims);
                 return true;
             }
             // if no axes are provided and NoopWithEmptyAxes is false, reduce on all axes
-            axes.resize(getInput(0)->nbDims());
-            std::iota(axes.begin(), axes.end(), 0);
+            reduced_axes.resize(getInput(0)->nbDims());
+            std::iota(reduced_axes.begin(), reduced_axes.end(), 0);
         }
 
-        if (mAttributes->template getAttr<ReduceSumAttr::KeepDims>()) {
-            std::for_each(axes.cbegin(), axes.cend(), [&outDims] (const std::int32_t& val) { outDims[val] = 1; });
+        if (keepDims()) {
+            std::for_each(reduced_axes.cbegin(), reduced_axes.cend(), [&outDims] (const std::int32_t& val) { outDims[val] = 1; });
         }
         else {
-            for (auto it = axes.crbegin(); it != axes.crend(); ++it)
+            for (auto it = reduced_axes.crbegin(); it != reduced_axes.crend(); ++it)
                 outDims.erase(outDims.begin() + static_cast<std::size_t>(*it));
         }
 
diff --git a/src/operator/Transpose.cpp b/src/operator/Transpose.cpp
index b550db16dfee8286242df7cfbed9b3b300ee96d5..f9d612353a5fe8764419d6ac2f7fe1702f2a5df8 100644
--- a/src/operator/Transpose.cpp
+++ b/src/operator/Transpose.cpp
@@ -35,7 +35,7 @@ const std::string Aidge::Transpose_Op::Type = "Transpose";
 Aidge::Transpose_Op::Transpose_Op(const std::vector<Aidge::DimSize_t> &outputDimsOrder)
     : OperatorTensor(Type, {InputCategory::Data}, 1),
         mAttributes(std::make_shared<Attributes_>(
-        attr<TransposeAttr::OutputDimsOrder>(outputDimsOrder)))
+        attr<Attr::OutputDimsOrder>(outputDimsOrder)))
 {
     mImpl = std::make_shared<TransposeImpl>(*this);
 }