diff --git a/.gitlab/ci/_global.gitlab-ci.yml b/.gitlab/ci/_global.gitlab-ci.yml
index aab5d745367d22052f82c6e3ef144680a822cd45..94e5658ff6adc8e07036d3d59ea39a68fbddc4bf 100644
--- a/.gitlab/ci/_global.gitlab-ci.yml
+++ b/.gitlab/ci/_global.gitlab-ci.yml
@@ -9,6 +9,14 @@ variables:
   GIT_SSL_NO_VERIFY: 1
   DEBIAN_FRONTEND: noninteractive
 
+# See https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines
+workflow:
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
+      when: never
+    - if: $CI_COMMIT_BRANCH
+
 default:
   image: nvidia/cuda:12.2.0-devel-ubuntu22.04
   before_script:
diff --git a/.gitlab/ci/build.gitlab-ci.yml b/.gitlab/ci/build.gitlab-ci.yml
index 6bfae0be1e31a89d27413677fa4cdc4612561333..a4579e2951ccbafc4335ae428c62eba94c0757e5 100644
--- a/.gitlab/ci/build.gitlab-ci.yml
+++ b/.gitlab/ci/build.gitlab-ci.yml
@@ -88,8 +88,7 @@ build:ubuntu_python:
     - virtualenv venv
     - source venv/bin/activate
     # Numpy dependancy for unit test
-    - python3 -m pip install numpy
-    - export AIDGE_INSTALL=`pwd`/install
+    - python3 -m pip install -r requirements.txt
     - python3 -m pip install .
   artifacts:
     expire_in: 1 week
@@ -147,8 +146,7 @@ build:windows_python:
     - virtualenv venv
     - venv\Scripts\Activate.ps1
     # Numpy dependancy for unit test
-    - python -m pip install numpy
-    - $env:AIDGE_INSTALL = "$pwd" + "install"
+    - python -m pip install -r requirements.txt
     - python -m pip install .
   artifacts:
     expire_in: 1 week
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 40d8837f41bdc0d8dfd7eac1c5960064967f1efb..f8dbe375e217020a4c4570bd67c1b466e6593130 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -6,7 +6,7 @@ file(READ "${CMAKE_SOURCE_DIR}/project_name.txt" project)
 message(STATUS "Project name: ${project}")
 message(STATUS "Project version: ${version}")
 
-# Note : project name is {project} and python module name is also {project} 
+# Note : project name is {project} and python module name is also {project}
 set(module_name _${project}) # target name
 
 
@@ -57,7 +57,7 @@ if (PYBIND)
 
     # Handles Python + pybind11 headers dependencies
     target_link_libraries(${module_name}
-        PUBLIC 
+        PUBLIC
             pybind11::pybind11
         PRIVATE
             Python::Python
@@ -101,8 +101,8 @@ install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
 install(EXPORT ${project}-targets
  FILE "${project}-targets.cmake"
  DESTINATION ${INSTALL_CONFIGDIR}
-#  COMPONENT ${module_name} 
-)  
+#  COMPONENT ${module_name}
+)
 
 #Create a ConfigVersion.cmake file
 include(CMakePackageConfigHelpers)
@@ -136,4 +136,4 @@ export(EXPORT ${project}-targets
 if(TEST)
     enable_testing()
     add_subdirectory(unit_tests)
-endif()
\ No newline at end of file
+endif()
diff --git a/aidge_core/__init__.py b/aidge_core/__init__.py
index ad18a8ef1b23625dcb52951f52c43adc4222c997..c65dcc6cfc4be8825d1213854014718fb7170854 100644
--- a/aidge_core/__init__.py
+++ b/aidge_core/__init__.py
@@ -8,3 +8,4 @@ http://www.eclipse.org/legal/epl-2.0.
 SPDX-License-Identifier: EPL-2.0
 """
 from aidge_core.aidge_core import * # import so generated by PyBind
+from aidge_core.export import ExportNode
diff --git a/aidge_core/export/__init__.py b/aidge_core/export/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..00b44121d68af06171525fdf953bf50e53328421
--- /dev/null
+++ b/aidge_core/export/__init__.py
@@ -0,0 +1 @@
+from .node_export import *
diff --git a/aidge_core/export/node_export.py b/aidge_core/export/node_export.py
new file mode 100644
index 0000000000000000000000000000000000000000..980cb05a5814b7476d64757353e393ad6130218b
--- /dev/null
+++ b/aidge_core/export/node_export.py
@@ -0,0 +1,61 @@
+import aidge_core
+
+from abc import ABC, abstractmethod
+
+
+class ExportNode(ABC):
+    """Abstract class to interface node with export generation.
+    """
+
+    @abstractmethod
+    def __init__(self, aidge_node: aidge_core.Node) -> None:
+        """Create ExportNode and retieve attirubtes from ``aidge_node``:
+
+        - name: aidge Node name
+        - attributes: dictionnary of attributes of the aidge Operator linked to the node, attributes name follow aidge naming convention
+        - parameters: List of parameters node, order in the list is the same as the one defined by the aidge operator
+
+        """
+        super().__init__()
+        self.node = aidge_node
+        self.operator = aidge_node.get_operator()
+        self.name = self.node.name()
+        self.attributes = {} # Attributes are auto fetched from aidge operators
+        if isinstance(self.operator, aidge_core.Attributes):
+            for attr_name in self.operator.get_attrs_name():
+                self.attributes[attr_name] = self.operator.get_attr(attr_name)
+
+        # rename is_leaf ?
+        self.is_last = len(self.node.get_children()) == 0
+
+
+        self.inputs = []
+        self.outputs = []
+        self.inputs_dims = []
+        self.outputs_dims = []
+
+        for idx, parent_node in enumerate(self.node.get_parents()):
+            self.inputs.append(parent_node)
+            if parent_node is not None:
+                self.inputs_dims.append(self.operator.input(idx).dims())
+            else:
+                self.inputs_dims.append(None)
+
+        for idx, child_node in enumerate(self.node.get_children()):
+            self.outputs.append(child_node)
+        
+        # Dirty hot fix, change it quickly
+        self.outputs_dims.append(self.operator.output(0).dims())
+
+    @abstractmethod
+    def export(self, export_folder:str, list_configs:list):
+        """Define how to export the node definition.
+        """
+        pass
+
+    @abstractmethod
+    def forward(self, list_actions:list):
+        """Define how to generate code to perform a forward pass.
+        """
+        pass
+
diff --git a/aidge_core/unit_tests/test_operator_binding.py b/aidge_core/unit_tests/test_operator_binding.py
index fc60f52274162155f8f891bf86c22c9a13b241f4..7bd1e730a973810db89aa786b52fa05c53c43590 100644
--- a/aidge_core/unit_tests/test_operator_binding.py
+++ b/aidge_core/unit_tests/test_operator_binding.py
@@ -102,5 +102,30 @@ class test_operator_binding(unittest.TestCase):
         genOp.get_operator().compute_output_dims()
         self.assertListEqual(genOp.get_operator().output(0).dims(), in_dims)
 
+    def test_set_impl(self):
+
+        class PythonCustomImpl(aidge_core.OperatorImpl):
+            """Dummy implementation to test that C++ call python code
+            """
+            def __init__(self, op: aidge_core.Operator):
+                aidge_core.OperatorImpl.__init__(self, op) # Recquired to avoid type error !
+                self.idx = 0
+
+            def forward(self):
+                """Increment idx attribute on forward.
+                """
+                self.idx += 1
+
+        generic_node = aidge_core.GenericOperator("Relu", 1, 1, 1, name="myReLu")
+        generic_op = generic_node.get_operator()
+        customImpl = PythonCustomImpl(generic_op)
+
+        generic_op.forward() # Do nothing, no implementation set
+        generic_op.set_impl(customImpl)
+        generic_op.forward() # Increment idx
+        self.assertEqual(customImpl.idx, 1)
+
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/include/aidge/aidge.hpp b/include/aidge/aidge.hpp
index c3f97f96e6b797afca7a28928f717691ae998185..e708c168421216fa249f26eee1f2b2eb80b588fd 100644
--- a/include/aidge/aidge.hpp
+++ b/include/aidge/aidge.hpp
@@ -38,6 +38,7 @@
 #include "aidge/operator/MetaOperator.hpp"
 #include "aidge/operator/MetaOperatorDefs.hpp"
 #include "aidge/operator/Operator.hpp"
+#include "aidge/operator/Pad.hpp"
 #include "aidge/operator/Producer.hpp"
 #include "aidge/operator/ReLU.hpp"
 #include "aidge/operator/Softmax.hpp"
diff --git a/include/aidge/backend/OperatorImpl.hpp b/include/aidge/backend/OperatorImpl.hpp
index 453e30a8636d86794c96723350bff615af090e3e..19f0837504016f38ae96dd852bc6fa41b5ab53ba 100644
--- a/include/aidge/backend/OperatorImpl.hpp
+++ b/include/aidge/backend/OperatorImpl.hpp
@@ -18,11 +18,13 @@
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
+class Operator;
+
 class OperatorImpl {
 public:
-
-    virtual void forward(){};
-    virtual void backward(){};
+    OperatorImpl(const Operator& op);
+    virtual void forward();
+    virtual void backward();
 
     /**
      * @brief Minimum amount of data from a specific input required by the
@@ -31,13 +33,13 @@ public:
      * @param inputIdx Index of the input analysed.
      * @return std::size_t
      */
-    virtual NbElts_t getNbRequiredData(const IOIndex_t inputIdx) const = 0;
+    virtual NbElts_t getNbRequiredData(const IOIndex_t inputIdx) const;
 
     // Amount of input data that cannot be overwritten during the execution.
-    virtual NbElts_t getNbRequiredProtected(const IOIndex_t inputIdx) const = 0;
+    virtual NbElts_t getNbRequiredProtected(const IOIndex_t inputIdx) const;
 
     // Memory required at an output for a given input size.
-    virtual NbElts_t getRequiredMemory(const IOIndex_t outputIdx, const std::vector<DimSize_t> &inputsSize) const = 0;
+    virtual NbElts_t getRequiredMemory(const IOIndex_t outputIdx, const std::vector<DimSize_t> &inputsSize) const;
 
     /**
      * @brief Total amount of consumed data from a specific input.
@@ -45,7 +47,7 @@ public:
      * @param inputIdx Index of the input analysed.
      * @return DimSize_t
      */
-    virtual NbElts_t getNbConsumedData(const IOIndex_t inputIdx) const = 0;
+    virtual NbElts_t getNbConsumedData(const IOIndex_t inputIdx) const;
 
     /**
      * @brief Total amount of produced data ready to be used on a specific output.
@@ -53,15 +55,20 @@ public:
      * @param outputIdx Index of the output analysed.
      * @return DimSize_t
      */
-    virtual NbElts_t getNbProducedData(const IOIndex_t outputIdx) const = 0;
+    virtual NbElts_t getNbProducedData(const IOIndex_t outputIdx) const;
 
     /**
      * @brief Update the Consummer Producer system by simulating the consumption and production of i/o
      *
      */
-    virtual void updateConsummerProducer() = 0;
+    virtual void updateConsummerProducer();
 
     virtual ~OperatorImpl() = default;
+
+protected:
+    const Operator &mOp;
+    std::vector<NbElts_t> mNbConsumedData;
+    std::vector<NbElts_t> mNbProducedData;
 };
 } // namespace Aidge
 
diff --git a/include/aidge/data/Tensor.hpp b/include/aidge/data/Tensor.hpp
index 7422a52eb171ee6dae0e14ad67c0562295fe5d8c..58c434bccc7c8dd39a93c46ecf74c38d7d834d1a 100644
--- a/include/aidge/data/Tensor.hpp
+++ b/include/aidge/data/Tensor.hpp
@@ -477,13 +477,14 @@ class Tensor : public Data,
         if (dims().empty()) { return "{}"; }
         std::string res;
         std::size_t dim = 0;
-        std::size_t *dimVals = new std::size_t[nbDims()];
-        for (std::size_t i = 0; i < nbDims(); ++i) {
-            dimVals[i] = 0;
-        }
         std::size_t counter = 0;
-        res += "{\n";
-        if (nbDims()>=2){
+        if (nbDims()>=2) {
+            std::size_t *dimVals = new std::size_t[nbDims()];
+            for (std::size_t i = 0; i < nbDims(); ++i) {
+                dimVals[i] = 0;
+            }
+            // std::vector<std::size_t> dimVals = std::vector<std::size_t>(nbDims(), 0);
+            res += "{\n";
             while (counter < mSize) {
                 std::string spaceString = std::string((dim+1)<<1,' ');
                 if (dim < nbDims()-2) {
@@ -532,31 +533,35 @@ class Tensor : public Data,
                         }
                         res += "\n";
                     }
+                    if (dim == 0) {
+                        break;
+                    }
                     dimVals[dim--] = 0;
                     dimVals[dim]++;
                 }
             }
-            for(int i = static_cast<int>(dim); i>=0; --i) {
+            delete[] dimVals;
+
+            for(int i = static_cast<int>(dim); i > 0; --i) {
                 res += std::string((dim+1)<<1,' ') + "}\n";
             }
-        }else{
+        } else {
+            res += "{";
             for (DimSize_t j = 0; j < dims()[0]; ++j) {
                 switch (mDataType)
                 {
                 case DataType::Int32:
-                    res += " " + std::to_string(static_cast<int *>(mImpl->rawPtr())[j]) + ((j < dims()[0]-1) ? "," : "\n");
+                    res += " " + std::to_string(static_cast<int *>(mImpl->rawPtr())[j]) + ((j < dims()[0]-1) ? "," : "");
                     break;
                 case DataType::Float64:
-                    res += " " + std::to_string(static_cast<double *>(mImpl->rawPtr())[j]) + ((j < dims()[0]-1) ? "," : "\n");
+                    res += " " + std::to_string(static_cast<double *>(mImpl->rawPtr())[j]) + ((j < dims()[0]-1) ? "," : "");
                     break;
                 default:
-                    res += " " + std::to_string(static_cast<float *>(mImpl->rawPtr())[j]) + ((j < dims()[0]-1) ? "," : "\n");
+                    res += " " + std::to_string(static_cast<float *>(mImpl->rawPtr())[j]) + ((j < dims()[0]-1) ? "," : "");
                     break;
                 }
             }
         }
-
-
         res += "}";
         return res;
     }
diff --git a/include/aidge/graphRegex/GraphRegex.hpp b/include/aidge/graphRegex/GraphRegex.hpp
index 83940cc7fc270e78a5f80db147267d5136c23936..a357462c2ac0153714bd85398058bf7c3085148e 100644
--- a/include/aidge/graphRegex/GraphRegex.hpp
+++ b/include/aidge/graphRegex/GraphRegex.hpp
@@ -21,7 +21,7 @@ class GraphRegex{
 
     std::vector<std::string> mQuery;
     std::vector<std::shared_ptr<ConditionalInterpreter>> mAllTest;
-    std::map<std::string, std::function<bool(NodePtr)>&> mAllLambda;
+    std::map<std::string, std::function<bool(NodePtr)>> mAllLambda;
 
     public:
     GraphRegex(){};
diff --git a/include/aidge/operator/Add.hpp b/include/aidge/operator/Add.hpp
index 57403270d44d66e87675a3cadb227342c0cacd91..65c7e8ce0e47bd470e2a1499a682ed2f2c8c2dbc 100644
--- a/include/aidge/operator/Add.hpp
+++ b/include/aidge/operator/Add.hpp
@@ -162,6 +162,12 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return NUM; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return NUM; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+        static const std::vector<std::string> getInputsName(){
+        return {"data_input_0", "data_input_n"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 template <std::size_t NUM>
diff --git a/include/aidge/operator/AvgPooling.hpp b/include/aidge/operator/AvgPooling.hpp
index be15ceb66ce32b98bfafab4af4213eee163dfbf9..dfcd0d5b3b4d892f201485e85710d42cd5b71dba 100644
--- a/include/aidge/operator/AvgPooling.hpp
+++ b/include/aidge/operator/AvgPooling.hpp
@@ -157,18 +157,23 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 template <std::array<DimSize_t, 1>::size_type DIM>
 inline std::shared_ptr<Node> AvgPooling(const std::array<DimSize_t, DIM> &kernel_dims,
                                            const std::string& name = "",
                                            const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1)) {
-    // FIXME: properly handle default w&b initialization in every cases
     static_assert(DIM<=MaxDim,"Too many kernel dimensions required by AvgPooling, not supported");
-    auto avgPool = std::make_shared<Node>(std::make_shared<AvgPooling_Op<static_cast<DimIdx_t>(DIM)>>(kernel_dims, stride_dims), name);
-    return avgPool;
+    return std::make_shared<Node>(std::make_shared<AvgPooling_Op<static_cast<DimIdx_t>(DIM)>>(kernel_dims, stride_dims), name);
 }
 
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
 template <DimSize_t DIM>
 inline std::shared_ptr<Node> AvgPooling(
     DimSize_t const (&kernel_dims)[DIM],
diff --git a/include/aidge/operator/BatchNorm.hpp b/include/aidge/operator/BatchNorm.hpp
index 75c901a1f00d26cc8b65192815c6fe93575723f0..da7360c8ba3816cdfe1d2d00f80b08808a80f961 100644
--- a/include/aidge/operator/BatchNorm.hpp
+++ b/include/aidge/operator/BatchNorm.hpp
@@ -160,6 +160,12 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 5; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input", "scale", "shift", "mean", "variance"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 template <DimSize_t DIM>
diff --git a/include/aidge/operator/Conv.hpp b/include/aidge/operator/Conv.hpp
index c8e229cbb3815ae7bd24064e862dc407b327febd..b1e3e34b0eff681632d90cb8314ebd8c96722eec 100644
--- a/include/aidge/operator/Conv.hpp
+++ b/include/aidge/operator/Conv.hpp
@@ -177,6 +177,12 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 3; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input", "weight", "bias"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 template <std::array<DimSize_t, 1>::size_type DIM>
@@ -195,6 +201,7 @@ inline std::shared_ptr<Node> Conv(DimSize_t in_channels,
     return conv;
 }
 
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
 template <DimSize_t DIM>
 inline std::shared_ptr<Node> Conv(
     DimSize_t in_channels,
diff --git a/include/aidge/operator/ConvDepthWise.hpp b/include/aidge/operator/ConvDepthWise.hpp
index 55a48a978f4bd515f31cff4feae79c3ab262b0e0..4caec2032a3c61529d452ae855f00c1da411af10 100644
--- a/include/aidge/operator/ConvDepthWise.hpp
+++ b/include/aidge/operator/ConvDepthWise.hpp
@@ -176,6 +176,12 @@ class ConvDepthWise_Op : public Operator,
     inline IOIndex_t nbInputs() const noexcept override final { return 3; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input", "weight", "bias"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 template <std::array<DimSize_t, 1>::size_type DIM>
@@ -191,6 +197,7 @@ inline std::shared_ptr<Node> ConvDepthWise(const std::array<DimSize_t, DIM> &ker
     return convDW;
 }
 
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
 template <DimSize_t DIM>
 inline std::shared_ptr<Node> ConvDepthWise(
     DimSize_t const (&kernel_dims)[DIM],
diff --git a/include/aidge/operator/FC.hpp b/include/aidge/operator/FC.hpp
index 8dea03ca0b7cd9ce7543fa35d082bc5164365b7b..b949527c51b9330077dd3bd8f8b4bf1f1b9d719c 100644
--- a/include/aidge/operator/FC.hpp
+++ b/include/aidge/operator/FC.hpp
@@ -158,6 +158,12 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 3; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input", "weight", "bias"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 inline std::shared_ptr<Node> FC(DimSize_t out_channels, bool noBias = false, const std::string& name = "") {
@@ -175,4 +181,4 @@ const char *const EnumStrings<Aidge::FCAttr>::data[] = {"OutChannels",
                                                         "NoBias"};
 }
 
-#endif /* AIDGE_CORE_OPERATOR_FC_H_ */
\ No newline at end of file
+#endif /* AIDGE_CORE_OPERATOR_FC_H_ */
diff --git a/include/aidge/operator/GenericOperator.hpp b/include/aidge/operator/GenericOperator.hpp
index 83b9a932633deb822ad86c24b96e6e928b5e2be2..55ccbf1516fa79663d57e1e44bc4017bc5c8b843 100644
--- a/include/aidge/operator/GenericOperator.hpp
+++ b/include/aidge/operator/GenericOperator.hpp
@@ -168,9 +168,20 @@ class GenericOperator_Op
 
     void setBackend(const std::string & /*name*/) override { printf("setBackend: not available yet.\n"); }
     void setDatatype(const DataType & /*datatype*/) override { printf("setDatatype: not available yet.\n"); }
-    void forward() override final { printf("forward: not available yet.\n"); }
-    void backward() override final { printf("backward: not available yet.\n"); }
-
+    void forward() override final {
+        if(mImpl){
+            mImpl->forward();
+        }else{
+            printf("forward: No implementation is linked.\n");
+        }
+    }
+    void backward() override final {
+        if(mImpl){
+            mImpl->backward();
+        }else{
+            printf("backward: No implementation is linked.\n");
+        }
+    }
     inline IOIndex_t nbInputs() const noexcept override final { return mNbIn; };
     inline IOIndex_t nbDataInputs() const noexcept override final { return mNbDataIn; };
     inline IOIndex_t nbOutputs() const noexcept override final { return mNbOut; };
diff --git a/include/aidge/operator/LeakyReLU.hpp b/include/aidge/operator/LeakyReLU.hpp
index 7a6fc4cbb8648b04aa42158c34d022b11775b84c..bcdcbc7cabd8eda46a7c0c4930f317e562fb46a0 100644
--- a/include/aidge/operator/LeakyReLU.hpp
+++ b/include/aidge/operator/LeakyReLU.hpp
@@ -137,10 +137,15 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+        static const std::vector<std::string> getInputsName(){
+        return {"data_input"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 inline std::shared_ptr<Node> LeakyReLU(float negativeSlope = 0.0f, const std::string& name = "") {
-    // FIXME: properly handle default w&b initialization in every cases
     return std::make_shared<Node>(std::make_shared<LeakyReLU_Op>(negativeSlope), name);
 }
 }
diff --git a/include/aidge/operator/MatMul.hpp b/include/aidge/operator/MatMul.hpp
index eec7072ff2739c80bb327f0e987e7d3712ba217e..eed1ec04535aa5896aa3d01a27d8023d37a42183 100644
--- a/include/aidge/operator/MatMul.hpp
+++ b/include/aidge/operator/MatMul.hpp
@@ -148,6 +148,12 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 2; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input", "weight"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 inline std::shared_ptr<Node> MatMul(DimSize_t out_channels, const std::string& name = "") {
diff --git a/include/aidge/operator/MaxPooling.hpp b/include/aidge/operator/MaxPooling.hpp
index bf802238c2dba8d13a0bb230750f3b882b6c09f5..bcf47f13cc34132f668ea1ffcb2c91ed6f06f44d 100644
--- a/include/aidge/operator/MaxPooling.hpp
+++ b/include/aidge/operator/MaxPooling.hpp
@@ -26,14 +26,15 @@
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
-enum class MaxPoolingAttr { StrideDims, KernelDims };
+enum class MaxPoolingAttr { StrideDims, KernelDims, CeilMode };
 
 template <DimIdx_t DIM>
 class MaxPooling_Op : public Operator,
                 public Registrable<MaxPooling_Op<DIM>, std::string, std::unique_ptr<OperatorImpl>(const MaxPooling_Op<DIM> &)>,
                 public StaticAttributes<MaxPoolingAttr,
                                        std::array<DimSize_t, DIM>,
-                                       std::array<DimSize_t, DIM>> {
+                                       std::array<DimSize_t, DIM>,
+                                       bool> {
 private:
     // FIXME: change accessibility
     std::shared_ptr<Tensor> mInput = std::make_shared<Tensor>();
@@ -46,15 +47,18 @@ public:
 
     using Attributes_ = StaticAttributes<MaxPoolingAttr,
                                              std::array<DimSize_t, DIM>,
-                                             std::array<DimSize_t, DIM>>;
+                                             std::array<DimSize_t, DIM>,
+                                             bool>;
     template <MaxPoolingAttr e>
     using attr = typename Attributes_::template attr<e>;
 
     constexpr MaxPooling_Op(const std::array<DimSize_t, DIM> &kernel_dims,
-                            const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1))
+                            const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
+                            bool ceil_mode = false)
         : Operator(Type),
           Attributes_(attr<MaxPoolingAttr::StrideDims>(stride_dims),
-                      attr<MaxPoolingAttr::KernelDims>(kernel_dims)),
+                      attr<MaxPoolingAttr::KernelDims>(kernel_dims),
+                      attr<MaxPoolingAttr::CeilMode>(ceil_mode)),
           mOutput(std::make_shared<Tensor>()) {
         setDatatype(DataType::Float32);
     }
@@ -93,9 +97,16 @@ public:
         if (!mInput->empty()) {
             std::array<DimSize_t, DIM + 2> outputDims = {};
 
+            std::function<float(float)> roundingFunction;
+            if (this->template getAttr<MaxPoolingAttr::CeilMode>()) {
+                roundingFunction = [](float x) { return std::ceil(x); };
+            } else {
+                roundingFunction = [](float x) { return std::floor(x); };
+            }
+
             for (std::size_t dim = 0; dim < this->template getAttr<MaxPoolingAttr::KernelDims>().size() ; ++dim) {
                 outputDims[dim+2] = 1 + static_cast<DimSize_t>(
-                                            std::floor(static_cast<float>(mInput->dims()[dim+2] -
+                                            roundingFunction(static_cast<float>(mInput->dims()[dim+2] -
                                                                     this->template getAttr<MaxPoolingAttr::KernelDims>()[dim]) /
                                             static_cast<float>(this->template getAttr<MaxPoolingAttr::StrideDims>()[dim])));
             }
@@ -158,31 +169,38 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 template <std::array<DimSize_t, 1>::size_type DIM>
 inline std::shared_ptr<Node> MaxPooling(const std::array<DimSize_t, DIM> &kernel_dims,
                                            const std::string& name = "",
-                                           const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1)) {
-    // FIXME: properly handle default w&b initialization in every cases
+                                           const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
+                                           bool ceil_mode=false) {
     static_assert(DIM<=MaxDim,"Too many kernel dimensions required by MaxPooling, not supported");
-    auto avgPool = std::make_shared<Node>(std::make_shared<MaxPooling_Op<static_cast<DimIdx_t>(DIM)>>(kernel_dims, stride_dims), name);
-    return avgPool;
+    return std::make_shared<Node>(std::make_shared<MaxPooling_Op<static_cast<DimIdx_t>(DIM)>>(kernel_dims, stride_dims, ceil_mode), name);
 }
 
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
 template <DimSize_t DIM>
 inline std::shared_ptr<Node> MaxPooling(
     DimSize_t const (&kernel_dims)[DIM],
     const std::string& name = "",
-    const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1)) {
+    const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
+    bool ceil_mode = false) {
     static_assert(DIM<=MaxDim,"Too many kernel dimensions required by MaxPooling, not supported");
-    return MaxPooling(to_array(kernel_dims), name, stride_dims);
+    return MaxPooling(to_array(kernel_dims), name, stride_dims, ceil_mode);
 }
 }  // namespace Aidge
 
 namespace {
 template <>
-const char *const EnumStrings<Aidge::MaxPoolingAttr>::data[] = {"StrideDims", "KernelDims"};
+const char *const EnumStrings<Aidge::MaxPoolingAttr>::data[] = {"StrideDims", "KernelDims", "CeilMode"};
 }
 
 #endif /* AIDGE_CORE_OPERATOR_MAXPOOLING_H_ */
diff --git a/include/aidge/operator/MetaOperator.hpp b/include/aidge/operator/MetaOperator.hpp
index bb34fd9c7756f103d4f31f17f815309c925306b7..72058dfcba6e811a01a22e261208741879638cad 100644
--- a/include/aidge/operator/MetaOperator.hpp
+++ b/include/aidge/operator/MetaOperator.hpp
@@ -156,9 +156,11 @@ public:
 
 inline std::shared_ptr<Node> MetaOperator(const char *type,
                                   const std::shared_ptr<GraphView>& graph,
-                                  const std::string& name = "")
+                                  const std::string& name = "",
+                                  std::vector<NodePtr> inputNodes = std::vector<NodePtr>(),
+                                  std::vector<NodePtr> outputNodes = std::vector<NodePtr>())
 {
-    return std::make_shared<Node>(std::make_shared<MetaOperator_Op>(type, graph), name);
+    return std::make_shared<Node>(std::make_shared<MetaOperator_Op>(type, graph, inputNodes, outputNodes), name);
 }
 }  // namespace Aidge
 
diff --git a/include/aidge/operator/MetaOperatorDefs.hpp b/include/aidge/operator/MetaOperatorDefs.hpp
index df66cec7e1accfee1518378ce2e9697cdc7f91fb..73feb134837787ae8d0d280dd723182c9d21438b 100644
--- a/include/aidge/operator/MetaOperatorDefs.hpp
+++ b/include/aidge/operator/MetaOperatorDefs.hpp
@@ -26,21 +26,22 @@ inline std::shared_ptr<Node> PaddedConv(DimSize_t in_channels,
                                   const std::array<DimSize_t, DIM> &kernel_dims,
                                   const std::string& name = "",
                                   const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-                                  const std::array<std::array<DimSize_t, 2>, DIM> &padding_dims = {0},
+                                  const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
                                   const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
 {
     // Construct micro-graph
-    auto pad = std::make_shared<Node>(std::make_shared<Pad_Op<static_cast<DimIdx_t>(DIM)>>(padding_dims, PadBorderType::Constant, 0.0), (!name.empty()) ? name + "_pad" : "");
+    auto pad = Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : "", PadBorderType::Constant, 0.0);
     auto conv = std::make_shared<Node>(std::make_shared<Conv_Op<static_cast<DimIdx_t>(DIM)>>(in_channels, out_channels, kernel_dims, stride_dims, dilation_dims), (!name.empty()) ? name + "_conv" : "");
     // Need to specify the ordered list of input operators
     const std::vector<NodePtr> orderedInputNodes = {pad, conv};
 
-    auto metaOp = std::make_shared<Node>(std::make_shared<MetaOperator_Op>("PaddedConv", Sequential({pad, conv}), orderedInputNodes), name);
+    auto metaOp = MetaOperator("PaddedConv", Sequential({pad, conv}), name, orderedInputNodes);
     addProducer(metaOp, 1, append(out_channels, append(in_channels, kernel_dims)), "w");
     addProducer(metaOp, 2, {out_channels}, "b");
     return metaOp;
 }
 
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
 template <DimSize_t DIM>
 inline std::shared_ptr<Node> PaddedConv(
     DimSize_t in_channels,
@@ -48,76 +49,93 @@ inline std::shared_ptr<Node> PaddedConv(
     DimSize_t const (&kernel_dims)[DIM],
     const std::string& name = "",
     const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-    const std::array<std::array<DimSize_t, 2>, DIM> &padding_dims = {0},
+    const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
     const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
 {
-    return PaddedConv<DIM>(in_channels, out_channels, to_array(kernel_dims), name, stride_dims, padding_dims, dilation_dims);
+    return PaddedConv(in_channels, out_channels, to_array(kernel_dims), name, stride_dims, padding_dims, dilation_dims);
 }
 
 template <std::array<DimSize_t, 1>::size_type DIM>
-inline std::shared_ptr<Node> PaddedConvDepthWise(DimSize_t in_channels,
-                                  DimSize_t out_channels,
-                                  const std::array<DimSize_t, DIM> &kernel_dims,
+inline std::shared_ptr<Node> PaddedConvDepthWise(const std::array<DimSize_t, DIM> &kernel_dims,
                                   const std::string& name = "",
                                   const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-                                  const std::array<std::array<DimSize_t, 2>, DIM> &padding_dims = {0},
+                                  const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
                                   const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
 {
     // Construct micro-graph
-    auto pad = std::make_shared<Node>(std::make_shared<Pad_Op<static_cast<DimIdx_t>(DIM)>>(padding_dims, PadBorderType::Constant, 0.0), (!name.empty()) ? name + "_pad" : "");
-    auto conv = std::make_shared<Node>(std::make_shared<ConvDepthWise_Op<static_cast<DimIdx_t>(DIM)>>(in_channels, out_channels, kernel_dims, stride_dims, dilation_dims), (!name.empty()) ? name + "_conv" : "");
+    auto pad = Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : "", PadBorderType::Constant, 0.0);
+    auto conv = std::make_shared<Node>(std::make_shared<ConvDepthWise_Op<static_cast<DimIdx_t>(DIM)>>(kernel_dims, stride_dims, dilation_dims), (!name.empty()) ? name + "_conv" : "");
     // Need to specify the ordered list of input operators
     const std::vector<NodePtr> orderedInputNodes = {pad, conv};
 
-    auto metaOp = std::make_shared<Node>(std::make_shared<MetaOperator_Op>("PaddedConvDepthWise", Sequential({pad, conv}), orderedInputNodes), name);
-    addProducer(metaOp, 1, append(out_channels, append(in_channels, kernel_dims)), "w");
-    addProducer(metaOp, 2, {out_channels}, "b");
+    auto metaOp = MetaOperator("PaddedConvDepthWise", Sequential({pad, conv}), name, orderedInputNodes);
+    addProducer(metaOp, 1, std::array<DimSize_t,0>({}), "w");
+    addProducer(metaOp, 2, std::array<DimSize_t,0>({}), "b");
     return metaOp;
 }
 
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
 template <DimSize_t DIM>
 inline std::shared_ptr<Node> PaddedConvDepthWise(
-    DimSize_t in_channels,
-    DimSize_t out_channels,
     DimSize_t const (&kernel_dims)[DIM],
     const std::string& name = "",
     const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-    const std::array<std::array<DimSize_t, 2>, DIM> &padding_dims = {0},
+    const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
     const std::array<DimSize_t, DIM> &dilation_dims = create_array<DimSize_t,DIM>(1))
 {
-    return PaddedConvDepthWise<DIM>(in_channels, out_channels, to_array(kernel_dims), name, stride_dims, padding_dims, dilation_dims);
+    return PaddedConvDepthWise(to_array(kernel_dims), name, stride_dims, padding_dims, dilation_dims);
 }
 
 template <std::array<DimSize_t, 1>::size_type DIM>
-inline std::shared_ptr<Node> PaddedAvgPooling(DimSize_t in_channels,
-                                  DimSize_t out_channels,
-                                  const std::array<DimSize_t, DIM> &kernel_dims,
+inline std::shared_ptr<Node> PaddedAvgPooling(const std::array<DimSize_t, DIM> &kernel_dims,
                                   const std::string& name = "",
                                   const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-                                  const std::array<std::array<DimSize_t, 2>, DIM> &padding_dims = {0})
+                                  const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0))
 {
     auto graph = Sequential({
         Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : ""),
-        AvgPooling_Op<DIM>(kernel_dims, (!name.empty()) ? name + "_avgpooling" : "", stride_dims)
+        AvgPooling(kernel_dims, (!name.empty()) ? name + "_avgpooling" : "", stride_dims)
     });
 
-    return std::make_shared<Node>(std::make_shared<MetaOperator_Op>("PaddedAvgPooling", graph), name);
+    return MetaOperator("PaddedAvgPooling", graph, name);
+}
+
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
+template <DimSize_t DIM>
+inline std::shared_ptr<Node> PaddedAvgPooling(
+    DimSize_t const (&kernel_dims)[DIM],
+    const std::string& name = "",
+    const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
+    const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0))
+{
+    return PaddedAvgPooling(to_array(kernel_dims), name, stride_dims, padding_dims);
 }
 
 template <std::array<DimSize_t, 1>::size_type DIM>
-inline std::shared_ptr<Node> PaddedMaxPooling(DimSize_t in_channels,
-                                  DimSize_t out_channels,
-                                  const std::array<DimSize_t, DIM> &kernel_dims,
+inline std::shared_ptr<Node> PaddedMaxPooling(const std::array<DimSize_t, DIM> &kernel_dims,
                                   const std::string& name = "",
                                   const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-                                  const std::array<std::array<DimSize_t, 2>, DIM> &padding_dims = {0})
+                                  const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
+                                  bool ceil_mode = false)
 {
     auto graph = Sequential({
         Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : ""),
-        MaxPooling_Op<DIM>(kernel_dims, (!name.empty()) ? name + "_maxpooling" : "", stride_dims)
+        MaxPooling(kernel_dims, (!name.empty()) ? name + "_maxpooling" : "", stride_dims, ceil_mode)
     });
 
-    return std::make_shared<Node>(std::make_shared<MetaOperator_Op>("PaddedMaxPooling", graph), name);
+    return MetaOperator("PaddedMaxPooling", graph, name);
+}
+
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
+template <DimSize_t DIM>
+inline std::shared_ptr<Node> PaddedMaxPooling(
+    DimSize_t const (&kernel_dims)[DIM],
+    const std::string& name = "",
+    const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
+    const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
+    bool ceil_mode= false)
+{
+    return PaddedMaxPooling(to_array(kernel_dims), name, stride_dims, padding_dims, ceil_mode);
 }
 }  // namespace Aidge
 
diff --git a/include/aidge/operator/Operator.hpp b/include/aidge/operator/Operator.hpp
index e3544171de9b97a2795f1d936adfeff341bd32dc..903b6362adf3db0c867dc419086e0cb6ddaa65c7 100644
--- a/include/aidge/operator/Operator.hpp
+++ b/include/aidge/operator/Operator.hpp
@@ -26,7 +26,7 @@ namespace Aidge {
 
 class Operator : public std::enable_shared_from_this<Operator> {
 protected:
-  std::unique_ptr<OperatorImpl> mImpl; // implementation of the operator
+  std::shared_ptr<OperatorImpl> mImpl; // implementation of the operator
   std::map<std::string, std::shared_ptr<Hook>> mHooks;
 
 private:
@@ -76,6 +76,14 @@ public:
     virtual void setBackend(const std::string& name) = 0;
     virtual void setDatatype(const DataType& datatype) = 0;
 
+    /**
+     * @brief Set the a new OperatorImpl to the Operator
+     *
+     */
+    void setImpl(std::shared_ptr<OperatorImpl> impl){
+        mImpl = impl;
+    }
+
     /**
      * @brief Minimum amount of data from a specific input for one computation pass.
      * @param inputIdx Index of the input analysed.
@@ -116,6 +124,12 @@ public:
     virtual IOIndex_t nbInputs() const noexcept = 0;
     virtual IOIndex_t nbDataInputs() const noexcept = 0;
     virtual IOIndex_t nbOutputs() const noexcept = 0;
+      static const std::vector<std::string> getInputsName(){
+        return {};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {};
+    }
 };
 } // namespace Aidge
 
diff --git a/include/aidge/operator/Pad.hpp b/include/aidge/operator/Pad.hpp
index deae0e6b8c5a91e5c10e7655549a4e46ac90eb0b..cbebb16e1e24501b0ea371fb45211047f6e2b5e7 100644
--- a/include/aidge/operator/Pad.hpp
+++ b/include/aidge/operator/Pad.hpp
@@ -27,13 +27,13 @@
 
 namespace Aidge {
 enum class PadAttr { BeginEndBorders, BorderType, BorderValue };
-enum class PadBorderType { Constant, Replicate, Reflect, Wrap };
+enum class PadBorderType { Constant, Edge, Reflect, Wrap };
 
 template <DimIdx_t DIM>
 class Pad_Op : public Operator,
                 public Registrable<Pad_Op<DIM>, std::string, std::unique_ptr<OperatorImpl>(const Pad_Op<DIM> &)>,
                 public StaticAttributes<PadAttr,
-                                       std::array<std::array<DimSize_t, 2>, DIM>,
+                                       std::array<DimSize_t, 2*DIM>,
                                        PadBorderType,
                                        double> {
 private:
@@ -47,13 +47,13 @@ public:
     Pad_Op() = delete;
 
     using Attributes_ = StaticAttributes<PadAttr,
-                                             std::array<std::array<DimSize_t, 2>, DIM>,
+                                             std::array<DimSize_t, 2*DIM>,
                                              PadBorderType,
                                              double>;
     template <PadAttr e>
     using attr = typename Attributes_::template attr<e>;
 
-    constexpr Pad_Op(const std::array<std::array<DimSize_t, 2>, DIM> &beginEndTuples,
+    constexpr Pad_Op(const std::array<DimSize_t, 2*DIM> &beginEndTuples,
                      const PadBorderType &borderType = PadBorderType::Constant,
                      double borderValue = 0.0)
         : Operator(Type),
@@ -97,9 +97,9 @@ public:
             std::array<DimSize_t, DIM + 2> outputDims = {};
 
             for (std::size_t dim = 0; dim < DIM; ++dim) {
-                outputDims[dim+2] = this->template getAttr<PadAttr::BeginEndBorders>()[dim][0]
+                outputDims[dim+2] = this->template getAttr<PadAttr::BeginEndBorders>()[2*dim]
                                     + mInput->dims()[dim+2]
-                                    + this->template getAttr<PadAttr::BeginEndBorders>()[dim][1];
+                                    + this->template getAttr<PadAttr::BeginEndBorders>()[2*dim+1];
             }
             outputDims[1] = mInput->dims()[1];
             outputDims[0] = mInput->dims()[0];
@@ -160,65 +160,33 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 template <std::array<DimSize_t, 1>::size_type DIM>
-inline std::shared_ptr<Node> Pad(const std::array<std::array<DimSize_t, 2>, DIM> &beginEndTuples,
-                                           const std::string& name = "",
-                                           const PadBorderType &borderType = PadBorderType::Constant,
-                                           double borderValue = 0.0)
-{
-    static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Pad, not supported");
-    auto pad = std::make_shared<Node>(std::make_shared<Pad_Op<static_cast<DimIdx_t>(DIM)>>(beginEndTuples, borderType, borderValue), name);
-    return pad;
-}
-
-template <std::array<DimSize_t, 1>::size_type DIM>
-inline std::shared_ptr<Node> Pad(const std::array<DimSize_t, DIM> &dimBeginEnd,
+inline std::shared_ptr<Node> Pad(const std::array<DimSize_t, 2*DIM> &beginEndTuples,
                                            const std::string& name = "",
                                            const PadBorderType &borderType = PadBorderType::Constant,
                                            double borderValue = 0.0)
 {
     static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Pad, not supported");
-    std::array<std::array<DimSize_t, 2>, DIM> beginEndTuples;
-    for (size_t i = 0; i < DIM; ++i) {
-        beginEndTuples[i] = {dimBeginEnd[i], dimBeginEnd[i]};
-    }
-    auto pad = std::make_shared<Node>(std::make_shared<Pad_Op<static_cast<DimIdx_t>(DIM)>>(beginEndTuples, borderType, borderValue), name);
-    return pad;
-}
-
-template <std::array<DimSize_t, 1>::size_type DIM>
-inline std::shared_ptr<Node> ZeroPad(const std::array<std::array<DimSize_t, 2>, DIM> &beginEndTuples,
-                                           const std::string& name = "")
-{
-    static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Pad, not supported");
-    auto pad = std::make_shared<Node>(std::make_shared<Pad_Op<static_cast<DimIdx_t>(DIM)>>(beginEndTuples, PadBorderType::Constant, 0.0), name);
-    return pad;
-}
-
-template <std::array<DimSize_t, 1>::size_type DIM>
-inline std::shared_ptr<Node> ZeroPad(const std::array<DimSize_t, DIM> &dimBeginEnd,
-                                           const std::string& name = "")
-{
-    static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Pad, not supported");
-    std::array<std::array<DimSize_t, 2>, DIM> beginEndTuples;
-    for (size_t i = 0; i < DIM; ++i) {
-        beginEndTuples[i] = {dimBeginEnd[i], dimBeginEnd[i]};
-    }
-    auto pad = std::make_shared<Node>(std::make_shared<Pad_Op<static_cast<DimIdx_t>(DIM)>>(beginEndTuples, PadBorderType::Constant, 0.0), name);
-    return pad;
+    return std::make_shared<Node>(std::make_shared<Pad_Op<static_cast<DimIdx_t>(DIM)>>(beginEndTuples, borderType, borderValue), name);
 }
 
+// helper with C-style array instead of std::array for beginEndTuples to allow automatic template DIM deduction
 template <DimSize_t DIM>
 inline std::shared_ptr<Node> Pad(
-    std::array<DimSize_t, 2> const (&beginEndTuples)[DIM],
+    DimSize_t const (&beginEndTuples)[2*DIM],
     const std::string& name = "",
     const PadBorderType &borderType = PadBorderType::Constant,
     double borderValue = 0.0)
 {
-    static_assert(DIM<=MaxDim,"Too many kernel dimensions required by Pad, not supported");
-    return Pad(to_array(beginEndTuples), name, borderType, borderValue);
+    return Pad<DIM>(to_array(beginEndTuples), name, borderType, borderValue);
 }
 }  // namespace Aidge
 
@@ -227,7 +195,7 @@ template <>
 const char *const EnumStrings<Aidge::PadAttr>::data[] = {"BeginEndBorders", "BorderType", "BorderValue"};
 
 template <>
-const char *const EnumStrings<Aidge::PadBorderType>::data[] = {"Constant", "Replicate", "Reflect", "Wrap"};
+const char *const EnumStrings<Aidge::PadBorderType>::data[] = {"Constant", "Edge", "Reflect", "Wrap"};
 }
 
 #endif /* AIDGE_CORE_OPERATOR_PAD_H_ */
diff --git a/include/aidge/operator/Producer.hpp b/include/aidge/operator/Producer.hpp
index 07d932bd0501832c78df2c3530f657b57251183f..d747b340618cc7e321f2cfc2ed9169798e5d77e9 100644
--- a/include/aidge/operator/Producer.hpp
+++ b/include/aidge/operator/Producer.hpp
@@ -79,7 +79,7 @@ public:
      * @brief Set the Output Tensor of the Producer operator.
      * This method will create a copy of the Tensor.
      *
-     * @param newOutput Tensor containing the values to copy 
+     * @param newOutput Tensor containing the values to copy
      */
     void setOutputTensor(const Tensor& newOutput) {
         *mOutput = newOutput;
@@ -132,6 +132,12 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 0; };
     inline IOIndex_t nbDataInputs() const noexcept override final { return 0; };
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; };
+    static const std::vector<std::string> getInputsName(){
+        return {};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 
 public:
   void forward() override final {
@@ -148,6 +154,7 @@ inline std::shared_ptr<Node> Producer(const std::array<DimSize_t, DIM> &dims, co
   return std::make_shared<Node>(std::make_shared<Producer_Op>(dims), name);
 }
 
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
 template <std::size_t DIM>
 inline std::shared_ptr<Node> Producer(DimSize_t const (&dims)[DIM], const std::string& name = "") {
   return Producer(to_array(dims), name);
@@ -167,6 +174,7 @@ void addProducer(std::shared_ptr<Node>& otherNode, const IOIndex_t inputIdx, con
     otherNode->getOperator()->associateInput(inputIdx, prod->getOperator()->getRawOutput(0));
 }
 
+// helper with C-style array instead of std::array for kernel_dims to allow automatic template DIM deduction
 template <std::size_t DIM>
 void addProducer(std::shared_ptr<Node>& otherNode, const IOIndex_t inputIdx, DimSize_t const (&dims)[DIM], const std::string& extension) {
     addProducer(otherNode, inputIdx, to_array(dims), extension);
diff --git a/include/aidge/operator/ReLU.hpp b/include/aidge/operator/ReLU.hpp
index 0a7ec3b4fd9b51dbdb7cc95cd111337dad8553c4..52f13f1c5ce1d0b7a0d4ccaa4d7fe9927bcc3e53 100644
--- a/include/aidge/operator/ReLU.hpp
+++ b/include/aidge/operator/ReLU.hpp
@@ -125,10 +125,15 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 inline std::shared_ptr<Node> ReLU(const std::string& name = "") {
-    // FIXME: properly handle default w&b initialization in every cases
     return std::make_shared<Node>(std::make_shared<ReLU_Op>(), name);
 }
 }
diff --git a/include/aidge/operator/Scaling.hpp b/include/aidge/operator/Scaling.hpp
index f18abaf320620bbffec646d1bbb752b834487dd4..353666fb3950d034a7dbe8ec1d3ebdb312679f95 100644
--- a/include/aidge/operator/Scaling.hpp
+++ b/include/aidge/operator/Scaling.hpp
@@ -146,6 +146,12 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 inline std::shared_ptr<Node> Scaling(float scalingFactor = 1.0f, const std::string& name = "") {
diff --git a/include/aidge/operator/Softmax.hpp b/include/aidge/operator/Softmax.hpp
index 095ea0aadb9b9684a472b8a437ace6f5151bc4cf..ba6132a5ee00325d0f7de57db117a169d42352e9 100644
--- a/include/aidge/operator/Softmax.hpp
+++ b/include/aidge/operator/Softmax.hpp
@@ -125,10 +125,15 @@ public:
     inline IOIndex_t nbInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbDataInputs() const noexcept override final { return 1; }
     inline IOIndex_t nbOutputs() const noexcept override final { return 1; }
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
 };
 
 inline std::shared_ptr<Node> Softmax(const std::string& name = "") {
-    // FIXME: properly handle default w&b initialization in every cases
     return std::make_shared<Node>(std::make_shared<Softmax_Op>(), name);
 }
 }
diff --git a/include/aidge/utils/DynamicAttributes.hpp b/include/aidge/utils/DynamicAttributes.hpp
index af03ee2861e81d81171ccc2ea14289f2ce3aa9e3..2af8f47e9420f266cc6eca21f167944c761db7ea 100644
--- a/include/aidge/utils/DynamicAttributes.hpp
+++ b/include/aidge/utils/DynamicAttributes.hpp
@@ -19,7 +19,7 @@
 #include <cassert>
 #include <string>
 
-#include "aidge/utils/Any.hpp"
+#include "aidge/utils/future_std/any.hpp"
 #include "aidge/utils/Attributes.hpp"
 
 #ifdef PYBIND
@@ -54,12 +54,12 @@ public:
             auto itPy = mAttrsPy.find(name);
             if (itPy != mAttrsPy.end()) {
                 // Insert the attribute back in C++
-                mAttrs.emplace(std::make_pair(name, libany::any(itPy->second.cast<T>())));
+                mAttrs.emplace(std::make_pair(name, future_std::any(itPy->second.cast<T>())));
             }
         }
 #endif
 
-        return libany::any_cast<T&>(mAttrs.at(name));
+        return future_std::any_cast<T&>(mAttrs.at(name));
     }
 
     template<class T> const T& getAttr(const std::string& name) const
@@ -71,12 +71,12 @@ public:
             auto itPy = mAttrsPy.find(name);
             if (itPy != mAttrsPy.end()) {
                 // Insert the attribute back in C++
-                mAttrs.emplace(std::make_pair(name, libany::any(itPy->second.cast<T>())));
+                mAttrs.emplace(std::make_pair(name, future_std::any(itPy->second.cast<T>())));
             }
         }
 #endif
 
-        return libany::any_cast<const T&>(mAttrs.at(name));
+        return future_std::any_cast<const T&>(mAttrs.at(name));
     }
 
     ///\brief Add a new Attribute, identified by its name. If it already exists, asserts.
@@ -85,7 +85,7 @@ public:
     ///\param value Attribute value
     template<class T> void addAttr(const std::string& name, const T& value)
     {
-        const auto& res = mAttrs.emplace(std::make_pair(name, libany::any(value)));
+        const auto& res = mAttrs.emplace(std::make_pair(name, future_std::any(value)));
         assert(res.second && "attribute already exists");
 
 #ifdef PYBIND
@@ -103,9 +103,9 @@ public:
     ///\param value Attribute value
     template<class T> void setAttr(const std::string& name, const T& value)
     {
-        auto res = mAttrs.emplace(std::make_pair(name, libany::any(value)));
+        auto res = mAttrs.emplace(std::make_pair(name, future_std::any(value)));
         if (!res.second)
-            res.first->second = libany::any(value);
+            res.first->second = future_std::any(value);
 
 #ifdef PYBIND
         // We cannot handle Python object if the Python interpreter is not running
@@ -210,9 +210,9 @@ private:
     std::map<std::string, py::object> mAttrsPy;
     // Stores C++ attributes only
     // mutable because it may be updated in getAttr() from Python
-    mutable std::map<std::string, libany::any> mAttrs;
+    mutable std::map<std::string, future_std::any> mAttrs;
 #else
-    std::map<std::string, libany::any> mAttrs;
+    std::map<std::string, future_std::any> mAttrs;
 #endif
 };
 
diff --git a/include/aidge/utils/Registrar.hpp b/include/aidge/utils/Registrar.hpp
index 3b29c472b3a540c9ef3b8ed46520e3e718e8cbfb..ece74509d466800c870d73d1e0bbe1d639f8bf54 100644
--- a/include/aidge/utils/Registrar.hpp
+++ b/include/aidge/utils/Registrar.hpp
@@ -35,7 +35,7 @@ public:
     {
         #ifdef PYBIND
         #define _CRT_SECURE_NO_WARNINGS
-        if (std::getenv("AIDGE_CORE_WITH_PYBIND")){
+        if (Py_IsInitialized()){
             std::string name = std::string("registrar_")+typeid(Registrable<DerivedClass, Key, Func>).name();
             static auto shared_data = reinterpret_cast<std::map<Key, std::function<Func>> *>(py::get_shared_data(name));
             if (!shared_data)
@@ -78,4 +78,4 @@ struct Registrar {
 };
 }
 
-#endif //AIDGE_CORE_UTILS_REGISTRAR_H_
\ No newline at end of file
+#endif //AIDGE_CORE_UTILS_REGISTRAR_H_
diff --git a/include/aidge/utils/Any.hpp b/include/aidge/utils/future_std/any.hpp
similarity index 98%
rename from include/aidge/utils/Any.hpp
rename to include/aidge/utils/future_std/any.hpp
index 0e65710596d31920de60a35d600e7ae612ea2bc4..8d9bfe28d0497dc12c59aaed68a23d3a9563815e 100644
--- a/include/aidge/utils/Any.hpp
+++ b/include/aidge/utils/future_std/any.hpp
@@ -14,11 +14,11 @@
  * Copyright (c) 2018 Claudio Fantacci
  *
  * Distributed under the Boost Software License, Version 1.0.
- * (See accompanying file LICENSE.md or copy at http://www.boost.org/LICENSE_1_0.txt)
+ * (See copy at http://www.boost.org/LICENSE_1_0.txt)
  */
 
-#ifndef AIDGE_CORE_UTILS_ANY_H_
-#define AIDGE_CORE_UTILS_ANY_H_
+#ifndef AIDGE_CORE_UTILS_FUTURE_STD_ANY_H_
+#define AIDGE_CORE_UTILS_FUTURE_STD_ANY_H_
 
 #include <stdexcept>
 #include <typeinfo>
@@ -26,7 +26,7 @@
 #include <utility>
 
 
-namespace libany
+namespace future_std
 {
 
 class bad_any_cast : public std::bad_cast
@@ -549,4 +549,4 @@ inline void swap(any& lhs, any& rhs) noexcept
 
 }
 
-#endif /* AIDGE_CORE_UTILS_ANY_H_ */
+#endif /* AIDGE_CORE_UTILS_FUTURE_STD_ANY_H_ */
diff --git a/include/aidge/utils/future_std/expected.hpp b/include/aidge/utils/future_std/expected.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c271d0e8d8066c0bcd0358f28f8bcd711a8b6ba0
--- /dev/null
+++ b/include/aidge/utils/future_std/expected.hpp
@@ -0,0 +1,3487 @@
+// Origin: https://github.com/martinmoene/expected-lite
+//
+// This version targets C++11 and later.
+//
+// Copyright (C) 2016-2020 Martin Moene.
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// expected lite is based on:
+//   A proposal to add a utility class to represent expected monad
+//   by Vicente J. Botet Escriba and Pierre Talbot. http:://wg21.link/p0323
+
+#ifndef AIDGE_CORE_UTILS_FUTURE_STD_EXPECTED_H_
+#define AIDGE_CORE_UTILS_FUTURE_STD_EXPECTED_H_
+
+#define expected_lite_MAJOR  0
+#define expected_lite_MINOR  6
+#define expected_lite_PATCH  3
+
+#define expected_lite_VERSION  expected_STRINGIFY(expected_lite_MAJOR) "." expected_STRINGIFY(expected_lite_MINOR) "." expected_STRINGIFY(expected_lite_PATCH)
+
+#define expected_STRINGIFY(  x )  expected_STRINGIFY_( x )
+#define expected_STRINGIFY_( x )  #x
+
+// expected-lite configuration:
+
+#define nsel_EXPECTED_DEFAULT  0
+#define nsel_EXPECTED_FUTURE_STD   1
+#define nsel_EXPECTED_STD      2
+
+// tweak header support:
+
+#ifdef __has_include
+# if __has_include(<future_std/expected.tweak.hpp>)
+#  include <future_std/expected.tweak.hpp>
+# endif
+#define expected_HAVE_TWEAK_HEADER  1
+#else
+#define expected_HAVE_TWEAK_HEADER  0
+//# pragma message("expected.hpp: Note: Tweak header not supported.")
+#endif
+
+// expected selection and configuration:
+
+#if !defined( nsel_CONFIG_SELECT_EXPECTED )
+# define nsel_CONFIG_SELECT_EXPECTED  ( nsel_HAVE_STD_EXPECTED ? nsel_EXPECTED_STD : nsel_EXPECTED_FUTURE_STD )
+#endif
+
+// Proposal revisions:
+//
+// DXXXXR0: --
+// N4015  : -2 (2014-05-26)
+// N4109  : -1 (2014-06-29)
+// P0323R0:  0 (2016-05-28)
+// P0323R1:  1 (2016-10-12)
+// -------:
+// P0323R2:  2 (2017-06-15)
+// P0323R3:  3 (2017-10-15)
+// P0323R4:  4 (2017-11-26)
+// P0323R5:  5 (2018-02-08)
+// P0323R6:  6 (2018-04-02)
+// P0323R7:  7 (2018-06-22) *
+//
+// expected-lite uses 2 and higher
+
+#ifndef  nsel_P0323R
+# define nsel_P0323R  7
+#endif
+
+// Monadic operations proposal revisions:
+//
+// P2505R0:  0 (2021-12-12)
+// P2505R1:  1 (2022-02-10)
+// P2505R2:  2 (2022-04-15)
+// P2505R3:  3 (2022-06-05)
+// P2505R4:  4 (2022-06-15)
+// P2505R5:  5 (2022-09-20) *
+//
+// expected-lite uses 5
+
+#ifndef  nsel_P2505R
+# define nsel_P2505R  5
+#endif
+
+// Control presence of C++ exception handling (try and auto discover):
+
+#ifndef nsel_CONFIG_NO_EXCEPTIONS
+# if defined(_MSC_VER)
+#  include <cstddef>    // for _HAS_EXCEPTIONS
+# endif
+# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS)
+#  define nsel_CONFIG_NO_EXCEPTIONS  0
+# else
+#  define nsel_CONFIG_NO_EXCEPTIONS  1
+# endif
+#endif
+
+// at default use SEH with MSVC for no C++ exceptions
+
+#ifndef  nsel_CONFIG_NO_EXCEPTIONS_SEH
+# define nsel_CONFIG_NO_EXCEPTIONS_SEH  ( nsel_CONFIG_NO_EXCEPTIONS && _MSC_VER )
+#endif
+
+// C++ language version detection (C++23 is speculative):
+// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
+
+#ifndef   nsel_CPLUSPLUS
+# if defined(_MSVC_LANG ) && !defined(__clang__)
+#  define nsel_CPLUSPLUS  (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
+# else
+#  define nsel_CPLUSPLUS  __cplusplus
+# endif
+#endif
+
+#define nsel_CPP98_OR_GREATER  ( nsel_CPLUSPLUS >= 199711L )
+#define nsel_CPP11_OR_GREATER  ( nsel_CPLUSPLUS >= 201103L )
+#define nsel_CPP14_OR_GREATER  ( nsel_CPLUSPLUS >= 201402L )
+#define nsel_CPP17_OR_GREATER  ( nsel_CPLUSPLUS >= 201703L )
+#define nsel_CPP20_OR_GREATER  ( nsel_CPLUSPLUS >= 202002L )
+#define nsel_CPP23_OR_GREATER  ( nsel_CPLUSPLUS >= 202300L )
+
+// Use C++23 std::expected if available and requested:
+
+#if nsel_CPP23_OR_GREATER && defined(__has_include )
+# if __has_include( <expected> )
+#  define nsel_HAVE_STD_EXPECTED  1
+# else
+#  define nsel_HAVE_STD_EXPECTED  0
+# endif
+#else
+# define  nsel_HAVE_STD_EXPECTED  0
+#endif
+
+#define  nsel_USES_STD_EXPECTED  ( (nsel_CONFIG_SELECT_EXPECTED == nsel_EXPECTED_STD) || ((nsel_CONFIG_SELECT_EXPECTED == nsel_EXPECTED_DEFAULT) && nsel_HAVE_STD_EXPECTED) )
+
+//
+// in_place: code duplicated in any-lite, expected-lite, expected-lite, value-ptr-lite, variant-lite:
+//
+
+#ifndef future_std_lite_HAVE_IN_PLACE_TYPES
+#define future_std_lite_HAVE_IN_PLACE_TYPES  1
+
+// C++17 std::in_place in <utility>:
+
+#if nsel_CPP17_OR_GREATER
+
+#include <utility>
+
+namespace future_std {
+
+using std::in_place;
+using std::in_place_type;
+using std::in_place_index;
+using std::in_place_t;
+using std::in_place_type_t;
+using std::in_place_index_t;
+
+#define future_std_lite_in_place_t(      T)  std::in_place_t
+#define future_std_lite_in_place_type_t( T)  std::in_place_type_t<T>
+#define future_std_lite_in_place_index_t(K)  std::in_place_index_t<K>
+
+#define future_std_lite_in_place(      T)    std::in_place_t{}
+#define future_std_lite_in_place_type( T)    std::in_place_type_t<T>{}
+#define future_std_lite_in_place_index(K)    std::in_place_index_t<K>{}
+
+} // namespace future_std
+
+#else // nsel_CPP17_OR_GREATER
+
+#include <cstddef>
+
+namespace future_std {
+namespace detail {
+
+template< class T >
+struct in_place_type_tag {};
+
+template< std::size_t K >
+struct in_place_index_tag {};
+
+} // namespace detail
+
+struct in_place_t {};
+
+template< class T >
+inline in_place_t in_place( detail::in_place_type_tag<T> = detail::in_place_type_tag<T>() )
+{
+    return in_place_t();
+}
+
+template< std::size_t K >
+inline in_place_t in_place( detail::in_place_index_tag<K> = detail::in_place_index_tag<K>() )
+{
+    return in_place_t();
+}
+
+template< class T >
+inline in_place_t in_place_type( detail::in_place_type_tag<T> = detail::in_place_type_tag<T>() )
+{
+    return in_place_t();
+}
+
+template< std::size_t K >
+inline in_place_t in_place_index( detail::in_place_index_tag<K> = detail::in_place_index_tag<K>() )
+{
+    return in_place_t();
+}
+
+// mimic templated typedef:
+
+#define future_std_lite_in_place_t(      T)  future_std::in_place_t(&)( future_std::detail::in_place_type_tag<T>  )
+#define future_std_lite_in_place_type_t( T)  future_std::in_place_t(&)( future_std::detail::in_place_type_tag<T>  )
+#define future_std_lite_in_place_index_t(K)  future_std::in_place_t(&)( future_std::detail::in_place_index_tag<K> )
+
+#define future_std_lite_in_place(      T)    future_std::in_place_type<T>
+#define future_std_lite_in_place_type( T)    future_std::in_place_type<T>
+#define future_std_lite_in_place_index(K)    future_std::in_place_index<K>
+
+} // namespace future_std
+
+#endif // nsel_CPP17_OR_GREATER
+#endif // future_std_lite_HAVE_IN_PLACE_TYPES
+
+//
+// Using std::expected:
+//
+
+#if nsel_USES_STD_EXPECTED
+
+#include <expected>
+
+namespace future_std {
+
+    using std::expected;
+//  ...
+}
+
+#else // nsel_USES_STD_EXPECTED
+
+#include <cassert>
+#include <exception>
+#include <functional>
+#include <initializer_list>
+#include <memory>
+#include <new>
+#include <system_error>
+#include <type_traits>
+#include <utility>
+
+// additional includes:
+
+#if nsel_CONFIG_NO_EXCEPTIONS
+# if nsel_CONFIG_NO_EXCEPTIONS_SEH
+#  include <windows.h>   // for ExceptionCodes
+# else
+// already included: <cassert>
+# endif
+#else
+# include <stdexcept>
+#endif
+
+// C++ feature usage:
+
+#if nsel_CPP11_OR_GREATER
+# define nsel_constexpr  constexpr
+#else
+# define nsel_constexpr  /*constexpr*/
+#endif
+
+#if nsel_CPP14_OR_GREATER
+# define nsel_constexpr14 constexpr
+#else
+# define nsel_constexpr14 /*constexpr*/
+#endif
+
+#if nsel_CPP17_OR_GREATER
+# define nsel_inline17 inline
+#else
+# define nsel_inline17 /*inline*/
+#endif
+
+// Compiler versions:
+//
+// MSVC++  6.0  _MSC_VER == 1200  nsel_COMPILER_MSVC_VERSION ==  60  (Visual Studio 6.0)
+// MSVC++  7.0  _MSC_VER == 1300  nsel_COMPILER_MSVC_VERSION ==  70  (Visual Studio .NET 2002)
+// MSVC++  7.1  _MSC_VER == 1310  nsel_COMPILER_MSVC_VERSION ==  71  (Visual Studio .NET 2003)
+// MSVC++  8.0  _MSC_VER == 1400  nsel_COMPILER_MSVC_VERSION ==  80  (Visual Studio 2005)
+// MSVC++  9.0  _MSC_VER == 1500  nsel_COMPILER_MSVC_VERSION ==  90  (Visual Studio 2008)
+// MSVC++ 10.0  _MSC_VER == 1600  nsel_COMPILER_MSVC_VERSION == 100  (Visual Studio 2010)
+// MSVC++ 11.0  _MSC_VER == 1700  nsel_COMPILER_MSVC_VERSION == 110  (Visual Studio 2012)
+// MSVC++ 12.0  _MSC_VER == 1800  nsel_COMPILER_MSVC_VERSION == 120  (Visual Studio 2013)
+// MSVC++ 14.0  _MSC_VER == 1900  nsel_COMPILER_MSVC_VERSION == 140  (Visual Studio 2015)
+// MSVC++ 14.1  _MSC_VER >= 1910  nsel_COMPILER_MSVC_VERSION == 141  (Visual Studio 2017)
+// MSVC++ 14.2  _MSC_VER >= 1920  nsel_COMPILER_MSVC_VERSION == 142  (Visual Studio 2019)
+
+#if defined(_MSC_VER) && !defined(__clang__)
+# define nsel_COMPILER_MSVC_VER      (_MSC_VER )
+# define nsel_COMPILER_MSVC_VERSION  (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900)) )
+#else
+# define nsel_COMPILER_MSVC_VER      0
+# define nsel_COMPILER_MSVC_VERSION  0
+#endif
+
+#define nsel_COMPILER_VERSION( major, minor, patch )  ( 10 * ( 10 * (major) + (minor) ) + (patch) )
+
+#if defined(__clang__)
+# define nsel_COMPILER_CLANG_VERSION  nsel_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__)
+#else
+# define nsel_COMPILER_CLANG_VERSION  0
+#endif
+
+#if defined(__GNUC__) && !defined(__clang__)
+# define nsel_COMPILER_GNUC_VERSION  nsel_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)
+#else
+# define nsel_COMPILER_GNUC_VERSION  0
+#endif
+
+// half-open range [lo..hi):
+//#define nsel_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) )
+
+// Method enabling
+
+#define nsel_REQUIRES_0(...) \
+    template< bool B = (__VA_ARGS__), typename std::enable_if<B, int>::type = 0 >
+
+#define nsel_REQUIRES_T(...) \
+    , typename std::enable_if< (__VA_ARGS__), int >::type = 0
+
+#define nsel_REQUIRES_R(R, ...) \
+    typename std::enable_if< (__VA_ARGS__), R>::type
+
+#define nsel_REQUIRES_A(...) \
+    , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr
+
+// Presence of language and library features:
+
+#ifdef _HAS_CPP0X
+# define nsel_HAS_CPP0X  _HAS_CPP0X
+#else
+# define nsel_HAS_CPP0X  0
+#endif
+
+//#define nsel_CPP11_140  (nsel_CPP11_OR_GREATER || nsel_COMPILER_MSVC_VER >= 1900)
+
+// Clang, GNUC, MSVC warning suppression macros:
+
+#ifdef __clang__
+# pragma clang diagnostic push
+#elif defined  __GNUC__
+# pragma  GCC  diagnostic push
+#endif // __clang__
+
+#if nsel_COMPILER_MSVC_VERSION >= 140
+# pragma warning( push )
+# define nsel_DISABLE_MSVC_WARNINGS(codes)  __pragma( warning(disable: codes) )
+#else
+# define nsel_DISABLE_MSVC_WARNINGS(codes)
+#endif
+
+#ifdef __clang__
+# define nsel_RESTORE_WARNINGS()  _Pragma("clang diagnostic pop")
+#elif defined __GNUC__
+# define nsel_RESTORE_WARNINGS()  _Pragma("GCC diagnostic pop")
+#elif nsel_COMPILER_MSVC_VERSION >= 140
+# define nsel_RESTORE_WARNINGS()  __pragma( warning( pop ) )
+#else
+# define nsel_RESTORE_WARNINGS()
+#endif
+
+// Suppress the following MSVC (GSL) warnings:
+// - C26409: Avoid calling new and delete explicitly, use std::make_unique<T> instead (r.11)
+
+nsel_DISABLE_MSVC_WARNINGS( 26409 )
+
+//
+// expected:
+//
+
+namespace future_std { namespace expected_lite {
+
+// type traits C++17:
+
+namespace std17 {
+
+#if nsel_CPP17_OR_GREATER
+
+using std::conjunction;
+using std::is_swappable;
+using std::is_nothrow_swappable;
+
+#else // nsel_CPP17_OR_GREATER
+
+namespace detail {
+
+using std::swap;
+
+struct is_swappable
+{
+    template< typename T, typename = decltype( swap( std::declval<T&>(), std::declval<T&>() ) ) >
+    static std::true_type test( int /* unused */);
+
+    template< typename >
+    static std::false_type test(...);
+};
+
+struct is_nothrow_swappable
+{
+    // wrap noexcept(expr) in separate function as work-around for VC140 (VS2015):
+
+    template< typename T >
+    static constexpr bool satisfies()
+    {
+        return noexcept( swap( std::declval<T&>(), std::declval<T&>() ) );
+    }
+
+    template< typename T >
+    static auto test( int ) -> std::integral_constant<bool, satisfies<T>()>{}
+
+    template< typename >
+    static auto test(...) -> std::false_type;
+};
+} // namespace detail
+
+// is [nothrow] swappable:
+
+template< typename T >
+struct is_swappable : decltype( detail::is_swappable::test<T>(0) ){};
+
+template< typename T >
+struct is_nothrow_swappable : decltype( detail::is_nothrow_swappable::test<T>(0) ){};
+
+// conjunction:
+
+template< typename... > struct conjunction : std::true_type{};
+template< typename B1 > struct conjunction<B1> : B1{};
+
+template< typename B1, typename... Bn >
+struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type{};
+
+#endif // nsel_CPP17_OR_GREATER
+
+} // namespace std17
+
+// type traits C++20:
+
+namespace std20 {
+
+#if defined(__cpp_lib_remove_cvref)
+
+using std::remove_cvref;
+
+#else
+
+template< typename T >
+struct remove_cvref
+{
+    typedef typename std::remove_cv< typename std::remove_reference<T>::type >::type type;
+};
+
+#endif
+
+} // namespace std20
+
+// forward declaration:
+
+template< typename T, typename E >
+class expected;
+
+namespace detail {
+
+#if nsel_P2505R >= 3
+template< typename T >
+struct is_expected : std::false_type {};
+
+template< typename T, typename E >
+struct is_expected< expected< T, E > > : std::true_type {};
+#endif // nsel_P2505R >= 3
+
+/// discriminated union to hold value or 'error'.
+
+template< typename T, typename E >
+class storage_t_noncopy_nonmove_impl
+{
+    template< typename, typename > friend class future_std::expected_lite::expected;
+
+public:
+    using value_type = T;
+    using error_type = E;
+
+    // no-op construction
+    storage_t_noncopy_nonmove_impl() {}
+    ~storage_t_noncopy_nonmove_impl() {}
+
+    explicit storage_t_noncopy_nonmove_impl( bool has_value )
+        : m_has_value( has_value )
+    {}
+
+    void construct_value()
+    {
+        new( &m_value ) value_type();
+    }
+
+    // void construct_value( value_type const & e )
+    // {
+    //     new( &m_value ) value_type( e );
+    // }
+
+    // void construct_value( value_type && e )
+    // {
+    //     new( &m_value ) value_type( std::move( e ) );
+    // }
+
+    template< class... Args >
+    void emplace_value( Args&&... args )
+    {
+        new( &m_value ) value_type( std::forward<Args>(args)...);
+    }
+
+    template< class U, class... Args >
+    void emplace_value( std::initializer_list<U> il, Args&&... args )
+    {
+        new( &m_value ) value_type( il, std::forward<Args>(args)... );
+    }
+
+    void destruct_value()
+    {
+        m_value.~value_type();
+    }
+
+    // void construct_error( error_type const & e )
+    // {
+    //     // new( &m_error ) error_type( e );
+    // }
+
+    // void construct_error( error_type && e )
+    // {
+    //     // new( &m_error ) error_type( std::move( e ) );
+    // }
+
+    template< class... Args >
+    void emplace_error( Args&&... args )
+    {
+        new( &m_error ) error_type( std::forward<Args>(args)...);
+    }
+
+    template< class U, class... Args >
+    void emplace_error( std::initializer_list<U> il, Args&&... args )
+    {
+        new( &m_error ) error_type( il, std::forward<Args>(args)... );
+    }
+
+    void destruct_error()
+    {
+        m_error.~error_type();
+    }
+
+    constexpr value_type const & value() const &
+    {
+        return m_value;
+    }
+
+    value_type & value() &
+    {
+        return m_value;
+    }
+
+    constexpr value_type const && value() const &&
+    {
+        return std::move( m_value );
+    }
+
+    nsel_constexpr14 value_type && value() &&
+    {
+        return std::move( m_value );
+    }
+
+    value_type const * value_ptr() const
+    {
+        return &m_value;
+    }
+
+    value_type * value_ptr()
+    {
+        return &m_value;
+    }
+
+    error_type const & error() const &
+    {
+        return m_error;
+    }
+
+    error_type & error() &
+    {
+        return m_error;
+    }
+
+    constexpr error_type const && error() const &&
+    {
+        return std::move( m_error );
+    }
+
+    nsel_constexpr14 error_type && error() &&
+    {
+        return std::move( m_error );
+    }
+
+    bool has_value() const
+    {
+        return m_has_value;
+    }
+
+    void set_has_value( bool v )
+    {
+        m_has_value = v;
+    }
+
+private:
+    union
+    {
+        value_type m_value;
+        error_type m_error;
+    };
+
+    bool m_has_value = false;
+};
+
+template< typename T, typename E >
+class storage_t_impl
+{
+    template< typename, typename > friend class future_std::expected_lite::expected;
+
+public:
+    using value_type = T;
+    using error_type = E;
+
+    // no-op construction
+    storage_t_impl() {}
+    ~storage_t_impl() {}
+
+    explicit storage_t_impl( bool has_value )
+        : m_has_value( has_value )
+    {}
+
+    void construct_value()
+    {
+        new( &m_value ) value_type();
+    }
+
+    void construct_value( value_type const & e )
+    {
+        new( &m_value ) value_type( e );
+    }
+
+    void construct_value( value_type && e )
+    {
+        new( &m_value ) value_type( std::move( e ) );
+    }
+
+    template< class... Args >
+    void emplace_value( Args&&... args )
+    {
+        new( &m_value ) value_type( std::forward<Args>(args)...);
+    }
+
+    template< class U, class... Args >
+    void emplace_value( std::initializer_list<U> il, Args&&... args )
+    {
+        new( &m_value ) value_type( il, std::forward<Args>(args)... );
+    }
+
+    void destruct_value()
+    {
+        m_value.~value_type();
+    }
+
+    void construct_error( error_type const & e )
+    {
+        new( &m_error ) error_type( e );
+    }
+
+    void construct_error( error_type && e )
+    {
+        new( &m_error ) error_type( std::move( e ) );
+    }
+
+    template< class... Args >
+    void emplace_error( Args&&... args )
+    {
+        new( &m_error ) error_type( std::forward<Args>(args)...);
+    }
+
+    template< class U, class... Args >
+    void emplace_error( std::initializer_list<U> il, Args&&... args )
+    {
+        new( &m_error ) error_type( il, std::forward<Args>(args)... );
+    }
+
+    void destruct_error()
+    {
+        m_error.~error_type();
+    }
+
+    constexpr value_type const & value() const &
+    {
+        return m_value;
+    }
+
+    value_type & value() &
+    {
+        return m_value;
+    }
+
+    constexpr value_type const && value() const &&
+    {
+        return std::move( m_value );
+    }
+
+    nsel_constexpr14 value_type && value() &&
+    {
+        return std::move( m_value );
+    }
+
+    value_type const * value_ptr() const
+    {
+        return &m_value;
+    }
+
+    value_type * value_ptr()
+    {
+        return &m_value;
+    }
+
+    error_type const & error() const &
+    {
+        return m_error;
+    }
+
+    error_type & error() &
+    {
+        return m_error;
+    }
+
+    constexpr error_type const && error() const &&
+    {
+        return std::move( m_error );
+    }
+
+    nsel_constexpr14 error_type && error() &&
+    {
+        return std::move( m_error );
+    }
+
+    bool has_value() const
+    {
+        return m_has_value;
+    }
+
+    void set_has_value( bool v )
+    {
+        m_has_value = v;
+    }
+
+private:
+    union
+    {
+        value_type m_value;
+        error_type m_error;
+    };
+
+    bool m_has_value = false;
+};
+
+/// discriminated union to hold only 'error'.
+
+template< typename E >
+struct storage_t_impl<void, E>
+{
+    template< typename, typename > friend class future_std::expected_lite::expected;
+
+public:
+    using value_type = void;
+    using error_type = E;
+
+    // no-op construction
+    storage_t_impl() {}
+    ~storage_t_impl() {}
+
+    explicit storage_t_impl( bool has_value )
+        : m_has_value( has_value )
+    {}
+
+    void construct_error( error_type const & e )
+    {
+        new( &m_error ) error_type( e );
+    }
+
+    void construct_error( error_type && e )
+    {
+        new( &m_error ) error_type( std::move( e ) );
+    }
+
+    template< class... Args >
+    void emplace_error( Args&&... args )
+    {
+        new( &m_error ) error_type( std::forward<Args>(args)...);
+    }
+
+    template< class U, class... Args >
+    void emplace_error( std::initializer_list<U> il, Args&&... args )
+    {
+        new( &m_error ) error_type( il, std::forward<Args>(args)... );
+    }
+
+    void destruct_error()
+    {
+        m_error.~error_type();
+    }
+
+    error_type const & error() const &
+    {
+        return m_error;
+    }
+
+    error_type & error() &
+    {
+        return m_error;
+    }
+
+    constexpr error_type const && error() const &&
+    {
+        return std::move( m_error );
+    }
+
+    nsel_constexpr14 error_type && error() &&
+    {
+        return std::move( m_error );
+    }
+
+    bool has_value() const
+    {
+        return m_has_value;
+    }
+
+    void set_has_value( bool v )
+    {
+        m_has_value = v;
+    }
+
+private:
+    union
+    {
+        char m_dummy;
+        error_type m_error;
+    };
+
+    bool m_has_value = false;
+};
+
+template< typename T, typename E, bool isConstructable, bool isMoveable >
+class storage_t
+{
+public:
+};
+
+template< typename T, typename E >
+class storage_t<T, E, false, false> : public storage_t_noncopy_nonmove_impl<T, E>
+{
+public:
+    storage_t() = default;
+    ~storage_t() = default;
+
+    explicit storage_t( bool has_value )
+        : storage_t_noncopy_nonmove_impl<T, E>( has_value )
+    {}
+
+    storage_t( storage_t const & other ) = delete;
+    storage_t( storage_t &&      other ) = delete;
+
+};
+
+template< typename T, typename E >
+class storage_t<T, E, true, true> : public storage_t_impl<T, E>
+{
+public:
+    storage_t() = default;
+    ~storage_t() = default;
+
+    explicit storage_t( bool has_value )
+        : storage_t_impl<T, E>( has_value )
+    {}
+
+    storage_t( storage_t const & other )
+        : storage_t_impl<T, E>( other.has_value() )
+    {
+        if ( this->has_value() ) this->construct_value( other.value() );
+        else                     this->construct_error( other.error() );
+    }
+
+    storage_t(storage_t && other )
+        : storage_t_impl<T, E>( other.has_value() )
+    {
+        if ( this->has_value() ) this->construct_value( std::move( other.value() ) );
+        else                     this->construct_error( std::move( other.error() ) );
+    }
+};
+
+template< typename E >
+class storage_t<void, E, true, true> : public storage_t_impl<void, E>
+{
+public:
+    storage_t() = default;
+    ~storage_t() = default;
+
+    explicit storage_t( bool has_value )
+        : storage_t_impl<void, E>( has_value )
+    {}
+
+    storage_t( storage_t const & other )
+        : storage_t_impl<void, E>( other.has_value() )
+    {
+        if ( this->has_value() ) ;
+        else                     this->construct_error( other.error() );
+    }
+
+    storage_t(storage_t && other )
+        : storage_t_impl<void, E>( other.has_value() )
+    {
+        if ( this->has_value() ) ;
+        else                     this->construct_error( std::move( other.error() ) );
+    }
+};
+
+template< typename T, typename E >
+class storage_t<T, E, true, false> : public storage_t_impl<T, E>
+{
+public:
+    storage_t() = default;
+    ~storage_t() = default;
+
+    explicit storage_t( bool has_value )
+        : storage_t_impl<T, E>( has_value )
+    {}
+
+    storage_t( storage_t const & other )
+        : storage_t_impl<T, E>(other.has_value())
+    {
+        if ( this->has_value() ) this->construct_value( other.value() );
+        else                     this->construct_error( other.error() );
+    }
+
+    storage_t( storage_t && other ) = delete;
+};
+
+template< typename E >
+class storage_t<void, E, true, false> : public storage_t_impl<void, E>
+{
+public:
+    storage_t() = default;
+    ~storage_t() = default;
+
+    explicit storage_t( bool has_value )
+        : storage_t_impl<void, E>( has_value )
+    {}
+
+    storage_t( storage_t const & other )
+        : storage_t_impl<void, E>(other.has_value())
+    {
+        if ( this->has_value() ) ;
+        else                     this->construct_error( other.error() );
+    }
+
+    storage_t( storage_t && other ) = delete;
+};
+
+template< typename T, typename E >
+class storage_t<T, E, false, true> : public storage_t_impl<T, E>
+{
+public:
+    storage_t() = default;
+    ~storage_t() = default;
+
+    explicit storage_t( bool has_value )
+        : storage_t_impl<T, E>( has_value )
+    {}
+
+    storage_t( storage_t const & other ) = delete;
+
+    storage_t( storage_t && other )
+        : storage_t_impl<T, E>( other.has_value() )
+    {
+        if ( this->has_value() ) this->construct_value( std::move( other.value() ) );
+        else                     this->construct_error( std::move( other.error() ) );
+    }
+};
+
+template< typename E >
+class storage_t<void, E, false, true> : public storage_t_impl<void, E>
+{
+public:
+    storage_t() = default;
+    ~storage_t() = default;
+
+    explicit storage_t( bool has_value )
+        : storage_t_impl<void, E>( has_value )
+    {}
+
+    storage_t( storage_t const & other ) = delete;
+
+    storage_t( storage_t && other )
+        : storage_t_impl<void, E>( other.has_value() )
+    {
+        if ( this->has_value() ) ;
+        else                     this->construct_error( std::move( other.error() ) );
+    }
+};
+
+#if nsel_P2505R >= 3
+// C++11 invoke implementation
+template< typename >
+struct is_reference_wrapper : std::false_type {};
+template< typename T >
+struct is_reference_wrapper< std::reference_wrapper< T > > : std::true_type {};
+
+template< typename FnT, typename ClassT, typename ObjectT, typename... Args
+    nsel_REQUIRES_T(
+        std::is_function<FnT>::value
+        && ( std::is_same< ClassT, typename std20::remove_cvref< ObjectT >::type >::value
+        || std::is_base_of< ClassT, typename std20::remove_cvref< ObjectT >::type >::value )
+    )
+>
+nsel_constexpr auto invoke_member_function_impl( FnT ClassT::* memfnptr, ObjectT && obj, Args && ... args )
+        noexcept( noexcept( (std::forward< ObjectT >( obj ).*memfnptr)( std::forward< Args >( args )... ) ) )
+        -> decltype( (std::forward< ObjectT >( obj ).*memfnptr)( std::forward< Args >( args )...) )
+{
+      return (std::forward< ObjectT >( obj ).*memfnptr)( std::forward< Args >( args )... );
+}
+
+template< typename FnT, typename ClassT, typename ObjectT, typename... Args
+    nsel_REQUIRES_T(
+        std::is_function<FnT>::value
+        && is_reference_wrapper< typename std20::remove_cvref< ObjectT >::type >::value
+    )
+>
+nsel_constexpr auto invoke_member_function_impl( FnT ClassT::* memfnptr, ObjectT && obj, Args && ... args )
+        noexcept( noexcept( (obj.get().*memfnptr)( std::forward< Args >( args ) ... ) ) )
+        -> decltype( (obj.get().*memfnptr)( std::forward< Args >( args ) ... ) )
+{
+    return (obj.get().*memfnptr)( std::forward< Args >( args ) ... );
+}
+
+template< typename FnT, typename ClassT, typename ObjectT, typename... Args
+    nsel_REQUIRES_T(
+        std::is_function<FnT>::value
+        && !std::is_same< ClassT, typename std20::remove_cvref< ObjectT >::type >::value
+        && !std::is_base_of< ClassT, typename std20::remove_cvref< ObjectT >::type >::value
+        && !is_reference_wrapper< typename std20::remove_cvref< ObjectT >::type >::value
+    )
+>
+nsel_constexpr auto invoke_member_function_impl( FnT ClassT::* memfnptr, ObjectT && obj, Args && ... args )
+        noexcept( noexcept( ((*std::forward< ObjectT >( obj )).*memfnptr)( std::forward< Args >( args ) ... ) ) )
+        -> decltype( ((*std::forward< ObjectT >( obj )).*memfnptr)( std::forward< Args >( args ) ... ) )
+{
+    return ((*std::forward<ObjectT>(obj)).*memfnptr)( std::forward< Args >( args ) ... );
+}
+
+template< typename MemberT, typename ClassT, typename ObjectT
+    nsel_REQUIRES_T(
+        std::is_same< ClassT, typename std20::remove_cvref< ObjectT >::type >::value
+        || std::is_base_of< ClassT, typename std20::remove_cvref< ObjectT >::type >::value
+    )
+>
+nsel_constexpr auto invoke_member_object_impl( MemberT ClassT::* memobjptr, ObjectT && obj )
+        noexcept( noexcept( std::forward< ObjectT >( obj ).*memobjptr ) )
+        -> decltype( std::forward< ObjectT >( obj ).*memobjptr )
+{
+    return std::forward< ObjectT >( obj ).*memobjptr;
+}
+
+template< typename MemberT, typename ClassT, typename ObjectT
+    nsel_REQUIRES_T(
+        is_reference_wrapper< typename std20::remove_cvref< ObjectT >::type >::value
+    )
+>
+nsel_constexpr auto invoke_member_object_impl( MemberT ClassT::* memobjptr, ObjectT && obj )
+        noexcept( noexcept( obj.get().*memobjptr ) )
+        -> decltype( obj.get().*memobjptr )
+{
+    return obj.get().*memobjptr;
+}
+
+template< typename MemberT, typename ClassT, typename ObjectT
+    nsel_REQUIRES_T(
+        !std::is_same< ClassT, typename std20::remove_cvref< ObjectT >::type >::value
+        && !std::is_base_of< ClassT, typename std20::remove_cvref< ObjectT >::type >::value
+        && !is_reference_wrapper< typename std20::remove_cvref< ObjectT >::type >::value
+    )
+>
+nsel_constexpr auto invoke_member_object_impl( MemberT ClassT::* memobjptr, ObjectT && obj )
+        noexcept( noexcept( (*std::forward< ObjectT >( obj )).*memobjptr ) )
+        -> decltype( (*std::forward< ObjectT >( obj )).*memobjptr )
+{
+    return (*std::forward< ObjectT >( obj )).*memobjptr;
+}
+
+template< typename F, typename... Args
+    nsel_REQUIRES_T(
+        std::is_member_function_pointer< typename std20::remove_cvref< F >::type >::value
+    )
+>
+nsel_constexpr auto invoke( F && f, Args && ... args )
+        noexcept( noexcept( invoke_member_function_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ) ) )
+        -> decltype( invoke_member_function_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ) )
+{
+    return invoke_member_function_impl( std::forward< F >( f ), std::forward< Args >( args ) ... );
+}
+
+template< typename F, typename... Args
+    nsel_REQUIRES_T(
+        std::is_member_object_pointer< typename std20::remove_cvref< F >::type >::value
+    )
+>
+nsel_constexpr auto invoke( F && f, Args && ... args )
+        noexcept( noexcept( invoke_member_object_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ) ) )
+        -> decltype( invoke_member_object_impl( std::forward< F >( f ), std::forward< Args >( args ) ... ) )
+{
+    return invoke_member_object_impl( std::forward< F >( f ), std::forward< Args >( args ) ... );
+}
+
+template< typename F, typename... Args
+    nsel_REQUIRES_T(
+        !std::is_member_function_pointer< typename std20::remove_cvref< F >::type >::value
+        && !std::is_member_object_pointer< typename std20::remove_cvref< F >::type >::value
+    )
+>
+nsel_constexpr auto invoke( F && f, Args && ... args )
+        noexcept( noexcept( std::forward< F >( f )( std::forward< Args >( args ) ... ) ) )
+        -> decltype( std::forward< F >( f )( std::forward< Args >( args ) ... ) )
+{
+    return std::forward< F >( f )( std::forward< Args >( args ) ... );
+}
+
+template< typename F, typename ... Args >
+using invoke_result_nocvref_t = typename std20::remove_cvref< decltype( invoke( std::declval< F >(), std::declval< Args >()... ) ) >::type;
+
+#if nsel_P2505R >= 5
+template< typename F, typename ... Args >
+using transform_invoke_result_t = typename std::remove_cv< decltype( invoke( std::declval< F >(), std::declval< Args >()... ) ) >::type;
+#else
+template< typename F, typename ... Args >
+using transform_invoke_result_t = invoke_result_nocvref_t
+#endif // nsel_P2505R >= 5
+
+template< typename T >
+struct valid_expected_value_type : std::integral_constant< bool, std::is_destructible< T >::value && !std::is_reference< T >::value && !std::is_array< T >::value > {};
+
+#endif // nsel_P2505R >= 3
+} // namespace detail
+
+/// x.x.5 Unexpected object type; unexpected_type; C++17 and later can also use aliased type unexpected.
+
+#if nsel_P0323R <= 2
+template< typename E = std::exception_ptr >
+class unexpected_type
+#else
+template< typename E >
+class unexpected_type
+#endif // nsel_P0323R
+{
+public:
+    using error_type = E;
+
+    // x.x.5.2.1 Constructors
+
+//  unexpected_type() = delete;
+
+    constexpr unexpected_type( unexpected_type const & ) = default;
+    constexpr unexpected_type( unexpected_type && ) = default;
+
+    template< typename... Args
+        nsel_REQUIRES_T(
+            std::is_constructible<E, Args&&...>::value
+        )
+    >
+    constexpr explicit unexpected_type( future_std_lite_in_place_t(E), Args &&... args )
+    : m_error( std::forward<Args>( args )...)
+    {}
+
+    template< typename U, typename... Args
+        nsel_REQUIRES_T(
+            std::is_constructible<E, std::initializer_list<U>, Args&&...>::value
+        )
+    >
+    constexpr explicit unexpected_type( future_std_lite_in_place_t(E), std::initializer_list<U> il, Args &&... args )
+    : m_error( il, std::forward<Args>( args )...)
+    {}
+
+    template< typename E2
+        nsel_REQUIRES_T(
+            std::is_constructible<E,E2>::value
+            && !std::is_same< typename std20::remove_cvref<E2>::type, future_std_lite_in_place_t(E2) >::value
+            && !std::is_same< typename std20::remove_cvref<E2>::type, unexpected_type >::value
+        )
+    >
+    constexpr explicit unexpected_type( E2 && error )
+    : m_error( std::forward<E2>( error ) )
+    {}
+
+    template< typename E2
+        nsel_REQUIRES_T(
+            std::is_constructible<    E, E2>::value
+            && !std::is_constructible<E, unexpected_type<E2>       &   >::value
+            && !std::is_constructible<E, unexpected_type<E2>           >::value
+            && !std::is_constructible<E, unexpected_type<E2> const &   >::value
+            && !std::is_constructible<E, unexpected_type<E2> const     >::value
+            && !std::is_convertible<     unexpected_type<E2>       &, E>::value
+            && !std::is_convertible<     unexpected_type<E2>        , E>::value
+            && !std::is_convertible<     unexpected_type<E2> const &, E>::value
+            && !std::is_convertible<     unexpected_type<E2> const  , E>::value
+            && !std::is_convertible< E2 const &, E>::value /*=> explicit */
+        )
+    >
+    constexpr explicit unexpected_type( unexpected_type<E2> const & error )
+    : m_error( E{ error.value() } )
+    {}
+
+    template< typename E2
+        nsel_REQUIRES_T(
+            std::is_constructible<    E, E2>::value
+            && !std::is_constructible<E, unexpected_type<E2>       &   >::value
+            && !std::is_constructible<E, unexpected_type<E2>           >::value
+            && !std::is_constructible<E, unexpected_type<E2> const &   >::value
+            && !std::is_constructible<E, unexpected_type<E2> const     >::value
+            && !std::is_convertible<     unexpected_type<E2>       &, E>::value
+            && !std::is_convertible<     unexpected_type<E2>        , E>::value
+            && !std::is_convertible<     unexpected_type<E2> const &, E>::value
+            && !std::is_convertible<     unexpected_type<E2> const  , E>::value
+            &&  std::is_convertible< E2 const &, E>::value /*=> explicit */
+        )
+    >
+    constexpr /*non-explicit*/ unexpected_type( unexpected_type<E2> const & error )
+    : m_error( error.value() )
+    {}
+
+    template< typename E2
+        nsel_REQUIRES_T(
+            std::is_constructible<    E, E2>::value
+            && !std::is_constructible<E, unexpected_type<E2>       &   >::value
+            && !std::is_constructible<E, unexpected_type<E2>           >::value
+            && !std::is_constructible<E, unexpected_type<E2> const &   >::value
+            && !std::is_constructible<E, unexpected_type<E2> const     >::value
+            && !std::is_convertible<     unexpected_type<E2>       &, E>::value
+            && !std::is_convertible<     unexpected_type<E2>        , E>::value
+            && !std::is_convertible<     unexpected_type<E2> const &, E>::value
+            && !std::is_convertible<     unexpected_type<E2> const  , E>::value
+            && !std::is_convertible< E2 const &, E>::value /*=> explicit */
+        )
+    >
+    constexpr explicit unexpected_type( unexpected_type<E2> && error )
+    : m_error( E{ std::move( error.value() ) } )
+    {}
+
+    template< typename E2
+        nsel_REQUIRES_T(
+            std::is_constructible<    E, E2>::value
+            && !std::is_constructible<E, unexpected_type<E2>       &   >::value
+            && !std::is_constructible<E, unexpected_type<E2>           >::value
+            && !std::is_constructible<E, unexpected_type<E2> const &   >::value
+            && !std::is_constructible<E, unexpected_type<E2> const     >::value
+            && !std::is_convertible<     unexpected_type<E2>       &, E>::value
+            && !std::is_convertible<     unexpected_type<E2>        , E>::value
+            && !std::is_convertible<     unexpected_type<E2> const &, E>::value
+            && !std::is_convertible<     unexpected_type<E2> const  , E>::value
+            &&  std::is_convertible< E2 const &, E>::value /*=> non-explicit */
+        )
+    >
+    constexpr /*non-explicit*/ unexpected_type( unexpected_type<E2> && error )
+    : m_error( std::move( error.value() ) )
+    {}
+
+    // x.x.5.2.2 Assignment
+
+    nsel_constexpr14 unexpected_type& operator=( unexpected_type const & ) = default;
+    nsel_constexpr14 unexpected_type& operator=( unexpected_type && ) = default;
+
+    template< typename E2 = E >
+    nsel_constexpr14 unexpected_type & operator=( unexpected_type<E2> const & other )
+    {
+        unexpected_type{ other.value() }.swap( *this );
+        return *this;
+    }
+
+    template< typename E2 = E >
+    nsel_constexpr14 unexpected_type & operator=( unexpected_type<E2> && other )
+    {
+        unexpected_type{ std::move( other.value() ) }.swap( *this );
+        return *this;
+    }
+
+    // x.x.5.2.3 Observers
+
+    nsel_constexpr14 E & value() & noexcept
+    {
+        return m_error;
+    }
+
+    constexpr E const & value() const & noexcept
+    {
+        return m_error;
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+    nsel_constexpr14 E && value() && noexcept
+    {
+        return std::move( m_error );
+    }
+
+    constexpr E const && value() const && noexcept
+    {
+        return std::move( m_error );
+    }
+
+#endif
+
+    // x.x.5.2.4 Swap
+
+    template< typename U=E >
+    nsel_REQUIRES_R( void,
+        std17::is_swappable<U>::value
+    )
+    swap( unexpected_type & other ) noexcept (
+        std17::is_nothrow_swappable<U>::value
+    )
+    {
+        using std::swap;
+        swap( m_error, other.m_error );
+    }
+
+    // TODO: ??? unexpected_type: in-class friend operator==, !=
+
+private:
+    error_type m_error;
+};
+
+#if nsel_CPP17_OR_GREATER
+
+/// template deduction guide:
+
+template< typename E >
+unexpected_type( E ) -> unexpected_type< E >;
+
+#endif
+
+/// class unexpected_type, std::exception_ptr specialization (P0323R2)
+
+#if !nsel_CONFIG_NO_EXCEPTIONS
+#if  nsel_P0323R <= 2
+
+// TODO: Should expected be specialized for particular E types such as exception_ptr and how?
+//       See p0323r7 2.1. Ergonomics, http://wg21.link/p0323
+template<>
+class unexpected_type< std::exception_ptr >
+{
+public:
+    using error_type = std::exception_ptr;
+
+    unexpected_type() = delete;
+
+    ~unexpected_type(){}
+
+    explicit unexpected_type( std::exception_ptr const & error )
+    : m_error( error )
+    {}
+
+    explicit unexpected_type(std::exception_ptr && error )
+    : m_error( std::move( error ) )
+    {}
+
+    template< typename E >
+    explicit unexpected_type( E error )
+    : m_error( std::make_exception_ptr( error ) )
+    {}
+
+    std::exception_ptr const & value() const
+    {
+        return m_error;
+    }
+
+    std::exception_ptr & value()
+    {
+        return m_error;
+    }
+
+private:
+    std::exception_ptr m_error;
+};
+
+#endif // nsel_P0323R
+#endif // !nsel_CONFIG_NO_EXCEPTIONS
+
+/// x.x.4, Unexpected equality operators
+
+template< typename E1, typename E2 >
+constexpr bool operator==( unexpected_type<E1> const & x, unexpected_type<E2> const & y )
+{
+    return x.value() == y.value();
+}
+
+template< typename E1, typename E2 >
+constexpr bool operator!=( unexpected_type<E1> const & x, unexpected_type<E2> const & y )
+{
+    return ! ( x == y );
+}
+
+#if nsel_P0323R <= 2
+
+template< typename E >
+constexpr bool operator<( unexpected_type<E> const & x, unexpected_type<E> const & y )
+{
+    return x.value() < y.value();
+}
+
+template< typename E >
+constexpr bool operator>( unexpected_type<E> const & x, unexpected_type<E> const & y )
+{
+    return ( y < x );
+}
+
+template< typename E >
+constexpr bool operator<=( unexpected_type<E> const & x, unexpected_type<E> const & y )
+{
+    return ! ( y < x  );
+}
+
+template< typename E >
+constexpr bool operator>=( unexpected_type<E> const & x, unexpected_type<E> const & y )
+{
+    return ! ( x < y );
+}
+
+#endif // nsel_P0323R
+
+/// x.x.5 Specialized algorithms
+
+template< typename E
+    nsel_REQUIRES_T(
+        std17::is_swappable<E>::value
+    )
+>
+void swap( unexpected_type<E> & x, unexpected_type<E> & y) noexcept ( noexcept ( x.swap(y) ) )
+{
+    x.swap( y );
+}
+
+#if nsel_P0323R <= 2
+
+// unexpected: relational operators for std::exception_ptr:
+
+inline constexpr bool operator<( unexpected_type<std::exception_ptr> const & /*x*/, unexpected_type<std::exception_ptr> const & /*y*/ )
+{
+    return false;
+}
+
+inline constexpr bool operator>( unexpected_type<std::exception_ptr> const & /*x*/, unexpected_type<std::exception_ptr> const & /*y*/ )
+{
+    return false;
+}
+
+inline constexpr bool operator<=( unexpected_type<std::exception_ptr> const & x, unexpected_type<std::exception_ptr> const & y )
+{
+    return ( x == y );
+}
+
+inline constexpr bool operator>=( unexpected_type<std::exception_ptr> const & x, unexpected_type<std::exception_ptr> const & y )
+{
+    return ( x == y );
+}
+
+#endif // nsel_P0323R
+
+// unexpected: traits
+
+#if nsel_P0323R <= 3
+
+template< typename E>
+struct is_unexpected : std::false_type {};
+
+template< typename E>
+struct is_unexpected< unexpected_type<E> > : std::true_type {};
+
+#endif // nsel_P0323R
+
+// unexpected: factory
+
+// keep make_unexpected() removed in p0323r2 for pre-C++17:
+
+template< typename E>
+nsel_constexpr14 auto
+make_unexpected( E && value ) -> unexpected_type< typename std::decay<E>::type >
+{
+    return unexpected_type< typename std::decay<E>::type >( std::forward<E>(value) );
+}
+
+#if nsel_P0323R <= 3
+
+/*nsel_constexpr14*/ auto inline
+make_unexpected_from_current_exception() -> unexpected_type< std::exception_ptr >
+{
+    return unexpected_type< std::exception_ptr >( std::current_exception() );
+}
+
+#endif // nsel_P0323R
+
+/// x.x.6, x.x.7 expected access error
+
+template< typename E >
+class bad_expected_access;
+
+/// x.x.7 bad_expected_access<void>: expected access error
+
+template <>
+class bad_expected_access< void > : public std::exception
+{
+public:
+    explicit bad_expected_access()
+    : std::exception()
+    {}
+};
+
+/// x.x.6 bad_expected_access: expected access error
+
+#if !nsel_CONFIG_NO_EXCEPTIONS
+
+template< typename E >
+class bad_expected_access : public bad_expected_access< void >
+{
+public:
+    using error_type = E;
+
+    explicit bad_expected_access( error_type error )
+    : m_error( error )
+    {}
+
+    virtual char const * what() const noexcept override
+    {
+        return "bad_expected_access";
+    }
+
+    nsel_constexpr14 error_type & error() &
+    {
+        return m_error;
+    }
+
+    constexpr error_type const & error() const &
+    {
+        return m_error;
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+    nsel_constexpr14 error_type && error() &&
+    {
+        return std::move( m_error );
+    }
+
+    constexpr error_type const && error() const &&
+    {
+        return std::move( m_error );
+    }
+
+#endif
+
+private:
+    error_type m_error;
+};
+
+#endif // nsel_CONFIG_NO_EXCEPTIONS
+
+/// x.x.8 unexpect tag, in_place_unexpected tag: construct an error
+
+struct unexpect_t{};
+using in_place_unexpected_t = unexpect_t;
+
+nsel_inline17 constexpr unexpect_t unexpect{};
+nsel_inline17 constexpr unexpect_t in_place_unexpected{};
+
+/// class error_traits
+
+#if nsel_CONFIG_NO_EXCEPTIONS
+
+namespace detail {
+    inline bool text( char const * /*text*/ ) { return true; }
+}
+
+template< typename Error >
+struct error_traits
+{
+    static void rethrow( Error const & /*e*/ )
+    {
+#if nsel_CONFIG_NO_EXCEPTIONS_SEH
+        RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL );
+#else
+        assert( false && detail::text("throw bad_expected_access<Error>{ e };") );
+#endif
+    }
+};
+
+template<>
+struct error_traits< std::exception_ptr >
+{
+    static void rethrow( std::exception_ptr const & /*e*/ )
+    {
+#if nsel_CONFIG_NO_EXCEPTIONS_SEH
+        RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL );
+#else
+        assert( false && detail::text("throw bad_expected_access<std::exception_ptr>{ e };") );
+#endif
+    }
+};
+
+template<>
+struct error_traits< std::error_code >
+{
+    static void rethrow( std::error_code const & /*e*/ )
+    {
+#if nsel_CONFIG_NO_EXCEPTIONS_SEH
+        RaiseException( EXCEPTION_ACCESS_VIOLATION, EXCEPTION_NONCONTINUABLE, 0, NULL );
+#else
+        assert( false && detail::text("throw std::system_error( e );") );
+#endif
+    }
+};
+
+#else // nsel_CONFIG_NO_EXCEPTIONS
+
+template< typename Error >
+struct error_traits
+{
+    static void rethrow( Error const & e )
+    {
+        throw bad_expected_access<Error>{ e };
+    }
+};
+
+template<>
+struct error_traits< std::exception_ptr >
+{
+    static void rethrow( std::exception_ptr const & e )
+    {
+        std::rethrow_exception( e );
+    }
+};
+
+template<>
+struct error_traits< std::error_code >
+{
+    static void rethrow( std::error_code const & e )
+    {
+        throw std::system_error( e );
+    }
+};
+
+#endif // nsel_CONFIG_NO_EXCEPTIONS
+
+#if nsel_P2505R >= 3
+namespace detail {
+
+// from https://en.cppreference.com/w/cpp/utility/expected/unexpected:
+// "the type of the unexpected value. The type must not be an array type, a non-object type, a specialization of std::unexpected, or a cv-qualified type."
+template< typename T >
+struct valid_unexpected_type : std::integral_constant< bool,
+    std::is_same< T, typename std20::remove_cvref< T >::type >::value
+    && std::is_object< T >::value
+    && !std::is_array< T >::value
+> {};
+
+template< typename T >
+struct valid_unexpected_type< unexpected_type< T > > : std::false_type {};
+
+} // namespace detail
+#endif // nsel_P2505R >= 3
+
+} // namespace expected_lite
+
+// provide future_std::unexpected_type:
+
+using expected_lite::unexpected_type;
+
+namespace expected_lite {
+
+/// class expected
+
+#if nsel_P0323R <= 2
+template< typename T, typename E = std::exception_ptr >
+class expected
+#else
+template< typename T, typename E >
+class expected
+#endif // nsel_P0323R
+{
+private:
+    template< typename, typename > friend class expected;
+
+public:
+    using value_type = T;
+    using error_type = E;
+    using unexpected_type = future_std::unexpected_type<E>;
+
+    template< typename U >
+    struct rebind
+    {
+        using type = expected<U, error_type>;
+    };
+
+    // x.x.4.1 constructors
+
+    nsel_REQUIRES_0(
+        std::is_default_constructible<T>::value
+    )
+    nsel_constexpr14 expected()
+    : contained( true )
+    {
+        contained.construct_value();
+    }
+
+    nsel_constexpr14 expected( expected const & ) = default;
+    nsel_constexpr14 expected( expected &&      ) = default;
+
+    template< typename U, typename G
+        nsel_REQUIRES_T(
+            std::is_constructible<    T, U const &>::value
+            &&  std::is_constructible<E, G const &>::value
+            && !std::is_constructible<T, expected<U, G>       &    >::value
+            && !std::is_constructible<T, expected<U, G>       &&   >::value
+            && !std::is_constructible<T, expected<U, G> const &    >::value
+            && !std::is_constructible<T, expected<U, G> const &&   >::value
+            && !std::is_convertible<     expected<U, G>       & , T>::value
+            && !std::is_convertible<     expected<U, G>       &&, T>::value
+            && !std::is_convertible<     expected<U, G> const & , T>::value
+            && !std::is_convertible<     expected<U, G> const &&, T>::value
+            && (!std::is_convertible<U const &, T>::value || !std::is_convertible<G const &, E>::value ) /*=> explicit */
+        )
+    >
+    nsel_constexpr14 explicit expected( expected<U, G> const & other )
+    : contained( other.has_value() )
+    {
+        if ( has_value() ) contained.construct_value( T{ other.contained.value() } );
+        else               contained.construct_error( E{ other.contained.error() } );
+    }
+
+    template< typename U, typename G
+        nsel_REQUIRES_T(
+            std::is_constructible<    T, U const &>::value
+            &&  std::is_constructible<E, G const &>::value
+            && !std::is_constructible<T, expected<U, G>       &    >::value
+            && !std::is_constructible<T, expected<U, G>       &&   >::value
+            && !std::is_constructible<T, expected<U, G> const &    >::value
+            && !std::is_constructible<T, expected<U, G> const &&   >::value
+            && !std::is_convertible<     expected<U, G>       & , T>::value
+            && !std::is_convertible<     expected<U, G>       &&, T>::value
+            && !std::is_convertible<     expected<U, G> const  &, T>::value
+            && !std::is_convertible<     expected<U, G> const &&, T>::value
+            && !(!std::is_convertible<U const &, T>::value || !std::is_convertible<G const &, E>::value ) /*=> non-explicit */
+        )
+    >
+    nsel_constexpr14 /*non-explicit*/ expected( expected<U, G> const & other )
+    : contained( other.has_value() )
+    {
+        if ( has_value() ) contained.construct_value( other.contained.value() );
+        else               contained.construct_error( other.contained.error() );
+    }
+
+    template< typename U, typename G
+        nsel_REQUIRES_T(
+            std::is_constructible<    T, U>::value
+            &&  std::is_constructible<E, G>::value
+            && !std::is_constructible<T, expected<U, G>       &    >::value
+            && !std::is_constructible<T, expected<U, G>       &&   >::value
+            && !std::is_constructible<T, expected<U, G> const &    >::value
+            && !std::is_constructible<T, expected<U, G> const &&   >::value
+            && !std::is_convertible<     expected<U, G>       & , T>::value
+            && !std::is_convertible<     expected<U, G>       &&, T>::value
+            && !std::is_convertible<     expected<U, G> const & , T>::value
+            && !std::is_convertible<     expected<U, G> const &&, T>::value
+            && (!std::is_convertible<U, T>::value || !std::is_convertible<G, E>::value ) /*=> explicit */
+        )
+    >
+    nsel_constexpr14 explicit expected( expected<U, G> && other )
+    : contained( other.has_value() )
+    {
+        if ( has_value() ) contained.construct_value( T{ std::move( other.contained.value() ) } );
+        else               contained.construct_error( E{ std::move( other.contained.error() ) } );
+    }
+
+    template< typename U, typename G
+        nsel_REQUIRES_T(
+            std::is_constructible<    T, U>::value
+            &&  std::is_constructible<E, G>::value
+            && !std::is_constructible<T, expected<U, G>      &     >::value
+            && !std::is_constructible<T, expected<U, G>      &&    >::value
+            && !std::is_constructible<T, expected<U, G> const &    >::value
+            && !std::is_constructible<T, expected<U, G> const &&   >::value
+            && !std::is_convertible<     expected<U, G>       & , T>::value
+            && !std::is_convertible<     expected<U, G>       &&, T>::value
+            && !std::is_convertible<     expected<U, G> const & , T>::value
+            && !std::is_convertible<     expected<U, G> const &&, T>::value
+            && !(!std::is_convertible<U, T>::value || !std::is_convertible<G, E>::value ) /*=> non-explicit */
+        )
+    >
+    nsel_constexpr14 /*non-explicit*/ expected( expected<U, G> && other )
+    : contained( other.has_value() )
+    {
+        if ( has_value() ) contained.construct_value( std::move( other.contained.value() ) );
+        else               contained.construct_error( std::move( other.contained.error() ) );
+    }
+
+    template< typename U = T
+        nsel_REQUIRES_T(
+            std::is_copy_constructible<U>::value
+        )
+    >
+    nsel_constexpr14 expected( value_type const & value )
+    : contained( true )
+    {
+        contained.construct_value( value );
+    }
+
+    template< typename U = T
+        nsel_REQUIRES_T(
+            std::is_constructible<T,U&&>::value
+            && !std::is_same<typename std20::remove_cvref<U>::type, future_std_lite_in_place_t(U)>::value
+            && !std::is_same<        expected<T,E>     , typename std20::remove_cvref<U>::type>::value
+            && !std::is_same<future_std::unexpected_type<E>, typename std20::remove_cvref<U>::type>::value
+            && !std::is_convertible<U&&,T>::value /*=> explicit */
+        )
+    >
+    nsel_constexpr14 explicit expected( U && value ) noexcept
+    (
+        std::is_nothrow_move_constructible<U>::value &&
+        std::is_nothrow_move_constructible<E>::value
+    )
+    : contained( true )
+    {
+        contained.construct_value( T{ std::forward<U>( value ) } );
+    }
+
+    template< typename U = T
+        nsel_REQUIRES_T(
+            std::is_constructible<T,U&&>::value
+            && !std::is_same<typename std20::remove_cvref<U>::type, future_std_lite_in_place_t(U)>::value
+            && !std::is_same<        expected<T,E>     , typename std20::remove_cvref<U>::type>::value
+            && !std::is_same<future_std::unexpected_type<E>, typename std20::remove_cvref<U>::type>::value
+            &&  std::is_convertible<U&&,T>::value /*=> non-explicit */
+        )
+    >
+    nsel_constexpr14 /*non-explicit*/ expected( U && value ) noexcept
+    (
+        std::is_nothrow_move_constructible<U>::value &&
+        std::is_nothrow_move_constructible<E>::value
+    )
+    : contained( true )
+    {
+        contained.construct_value( std::forward<U>( value ) );
+    }
+
+    // construct error:
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_constructible<E, G const &   >::value
+            && !std::is_convertible< G const &, E>::value /*=> explicit */
+        )
+    >
+    nsel_constexpr14 explicit expected( future_std::unexpected_type<G> const & error )
+    : contained( false )
+    {
+        contained.construct_error( E{ error.value() } );
+    }
+
+    template< typename G = E
+            nsel_REQUIRES_T(
+            std::is_constructible<E, G const &   >::value
+            && std::is_convertible<  G const &, E>::value /*=> non-explicit */
+        )
+    >
+    nsel_constexpr14 /*non-explicit*/ expected( future_std::unexpected_type<G> const & error )
+    : contained( false )
+    {
+        contained.construct_error( error.value() );
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_constructible<E, G&&   >::value
+            && !std::is_convertible< G&&, E>::value /*=> explicit */
+        )
+    >
+    nsel_constexpr14 explicit expected( future_std::unexpected_type<G> && error )
+    : contained( false )
+    {
+        contained.construct_error( E{ std::move( error.value() ) } );
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_constructible<E, G&&   >::value
+            && std::is_convertible<  G&&, E>::value /*=> non-explicit */
+        )
+    >
+    nsel_constexpr14 /*non-explicit*/ expected( future_std::unexpected_type<G> && error )
+    : contained( false )
+    {
+        contained.construct_error( std::move( error.value() ) );
+    }
+
+    // in-place construction, value
+
+    template< typename... Args
+        nsel_REQUIRES_T(
+            std::is_constructible<T, Args&&...>::value
+        )
+    >
+    nsel_constexpr14 explicit expected( future_std_lite_in_place_t(T), Args&&... args )
+    : contained( true )
+    {
+        contained.emplace_value( std::forward<Args>( args )... );
+    }
+
+    template< typename U, typename... Args
+        nsel_REQUIRES_T(
+            std::is_constructible<T, std::initializer_list<U>, Args&&...>::value
+        )
+    >
+    nsel_constexpr14 explicit expected( future_std_lite_in_place_t(T), std::initializer_list<U> il, Args&&... args )
+    : contained( true )
+    {
+        contained.emplace_value( il, std::forward<Args>( args )... );
+    }
+
+    // in-place construction, error
+
+    template< typename... Args
+        nsel_REQUIRES_T(
+            std::is_constructible<E, Args&&...>::value
+        )
+    >
+    nsel_constexpr14 explicit expected( unexpect_t, Args&&... args )
+    : contained( false )
+    {
+        contained.emplace_error( std::forward<Args>( args )... );
+    }
+
+    template< typename U, typename... Args
+        nsel_REQUIRES_T(
+            std::is_constructible<E, std::initializer_list<U>, Args&&...>::value
+        )
+    >
+    nsel_constexpr14 explicit expected( unexpect_t, std::initializer_list<U> il, Args&&... args )
+    : contained( false )
+    {
+        contained.emplace_error( il, std::forward<Args>( args )... );
+    }
+
+    // x.x.4.2 destructor
+
+    // TODO: ~expected: triviality
+    // Effects: If T is not cv void and is_trivially_destructible_v<T> is false and bool(*this), calls val.~T(). If is_trivially_destructible_v<E> is false and !bool(*this), calls unexpect.~unexpected<E>().
+    // Remarks: If either T is cv void or is_trivially_destructible_v<T> is true, and is_trivially_destructible_v<E> is true, then this destructor shall be a trivial destructor.
+
+    ~expected()
+    {
+        if ( has_value() ) contained.destruct_value();
+        else               contained.destruct_error();
+    }
+
+    // x.x.4.3 assignment
+
+    expected & operator=( expected const & other )
+    {
+        expected( other ).swap( *this );
+        return *this;
+    }
+
+    expected & operator=( expected && other ) noexcept
+    (
+        std::is_nothrow_move_constructible<   T>::value
+        && std::is_nothrow_move_assignable<   T>::value
+        && std::is_nothrow_move_constructible<E>::value     // added for missing
+        && std::is_nothrow_move_assignable<   E>::value )   //   nothrow above
+    {
+        expected( std::move( other ) ).swap( *this );
+        return *this;
+    }
+
+    template< typename U
+        nsel_REQUIRES_T(
+            !std::is_same<expected<T,E>, typename std20::remove_cvref<U>::type>::value
+            && std17::conjunction<std::is_scalar<T>, std::is_same<T, std::decay<U>> >::value
+            && std::is_constructible<T ,U>::value
+            && std::is_assignable<   T&,U>::value
+            && std::is_nothrow_move_constructible<E>::value )
+    >
+    expected & operator=( U && value )
+    {
+        expected( std::forward<U>( value ) ).swap( *this );
+        return *this;
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_constructible<E, G const&>::value &&
+            std::is_copy_constructible<G>::value    // TODO: std::is_nothrow_copy_constructible<G>
+            && std::is_copy_assignable<G>::value
+        )
+    >
+    expected & operator=( future_std::unexpected_type<G> const & error )
+    {
+        expected( unexpect, error.value() ).swap( *this );
+        return *this;
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_constructible<E, G&&>::value &&
+            std::is_move_constructible<G>::value    // TODO: std::is_nothrow_move_constructible<G>
+            && std::is_move_assignable<G>::value
+        )
+    >
+    expected & operator=( future_std::unexpected_type<G> && error )
+    {
+        expected( unexpect, std::move( error.value() ) ).swap( *this );
+        return *this;
+    }
+
+    template< typename... Args
+        nsel_REQUIRES_T(
+            std::is_nothrow_constructible<T, Args&&...>::value
+        )
+    >
+    value_type & emplace( Args &&... args )
+    {
+        expected( future_std_lite_in_place(T), std::forward<Args>(args)... ).swap( *this );
+        return value();
+    }
+
+    template< typename U, typename... Args
+        nsel_REQUIRES_T(
+            std::is_nothrow_constructible<T, std::initializer_list<U>&, Args&&...>::value
+        )
+    >
+    value_type & emplace( std::initializer_list<U> il, Args &&... args )
+    {
+        expected( future_std_lite_in_place(T), il, std::forward<Args>(args)... ).swap( *this );
+        return value();
+    }
+
+    // x.x.4.4 swap
+
+    template< typename U=T, typename G=E >
+    nsel_REQUIRES_R( void,
+        std17::is_swappable<   U>::value
+        && std17::is_swappable<G>::value
+        && ( std::is_move_constructible<U>::value || std::is_move_constructible<G>::value )
+    )
+    swap( expected & other ) noexcept
+    (
+        std::is_nothrow_move_constructible<T>::value && std17::is_nothrow_swappable<T&>::value &&
+        std::is_nothrow_move_constructible<E>::value && std17::is_nothrow_swappable<E&>::value
+    )
+    {
+        using std::swap;
+
+        if      (   bool(*this) &&   bool(other) ) { swap( contained.value(), other.contained.value() ); }
+        else if ( ! bool(*this) && ! bool(other) ) { swap( contained.error(), other.contained.error() ); }
+        else if (   bool(*this) && ! bool(other) ) { error_type t( std::move( other.error() ) );
+                                                     other.contained.destruct_error();
+                                                     other.contained.construct_value( std::move( contained.value() ) );
+                                                     contained.destruct_value();
+                                                     contained.construct_error( std::move( t ) );
+                                                     bool has_value = contained.has_value();
+                                                     bool other_has_value = other.has_value();
+                                                     other.contained.set_has_value(has_value);
+                                                     contained.set_has_value(other_has_value);
+                                                   }
+        else if ( ! bool(*this) &&   bool(other) ) { other.swap( *this ); }
+    }
+
+    // x.x.4.5 observers
+
+    constexpr value_type const * operator ->() const
+    {
+        return assert( has_value() ), contained.value_ptr();
+    }
+
+    value_type * operator ->()
+    {
+        return assert( has_value() ), contained.value_ptr();
+    }
+
+    constexpr value_type const & operator *() const &
+    {
+        return assert( has_value() ), contained.value();
+    }
+
+    value_type & operator *() &
+    {
+        return assert( has_value() ), contained.value();
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+    constexpr value_type const && operator *() const &&
+    {
+        return std::move( ( assert( has_value() ), contained.value() ) );
+    }
+
+    nsel_constexpr14 value_type && operator *() &&
+    {
+        return std::move( ( assert( has_value() ), contained.value() ) );
+    }
+
+#endif
+
+    constexpr explicit operator bool() const noexcept
+    {
+        return has_value();
+    }
+
+    constexpr bool has_value() const noexcept
+    {
+        return contained.has_value();
+    }
+
+    constexpr value_type const & value() const &
+    {
+        return has_value()
+            ? ( contained.value() )
+            : ( error_traits<error_type>::rethrow( contained.error() ), contained.value() );
+    }
+
+    value_type & value() &
+    {
+        return has_value()
+            ? ( contained.value() )
+            : ( error_traits<error_type>::rethrow( contained.error() ), contained.value() );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+    constexpr value_type const && value() const &&
+    {
+        return std::move( has_value()
+            ? ( contained.value() )
+            : ( error_traits<error_type>::rethrow( contained.error() ), contained.value() ) );
+    }
+
+    nsel_constexpr14 value_type && value() &&
+    {
+        return std::move( has_value()
+            ? ( contained.value() )
+            : ( error_traits<error_type>::rethrow( contained.error() ), contained.value() ) );
+    }
+
+#endif
+
+    constexpr error_type const & error() const &
+    {
+        return assert( ! has_value() ), contained.error();
+    }
+
+    error_type & error() &
+    {
+        return assert( ! has_value() ), contained.error();
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+    constexpr error_type const && error() const &&
+    {
+        return std::move( ( assert( ! has_value() ), contained.error() ) );
+    }
+
+    error_type && error() &&
+    {
+        return std::move( ( assert( ! has_value() ), contained.error() ) );
+    }
+
+#endif
+
+    constexpr unexpected_type get_unexpected() const
+    {
+        return make_unexpected( contained.error() );
+    }
+
+    template< typename Ex >
+    bool has_exception() const
+    {
+        using ContainedEx = typename std::remove_reference< decltype( get_unexpected().value() ) >::type;
+        return ! has_value() && std::is_base_of< Ex, ContainedEx>::value;
+    }
+
+    template< typename U
+        nsel_REQUIRES_T(
+            std::is_copy_constructible< T>::value
+            && std::is_convertible<U&&, T>::value
+        )
+    >
+    value_type value_or( U && v ) const &
+    {
+        return has_value()
+            ? contained.value()
+            : static_cast<T>( std::forward<U>( v ) );
+    }
+
+    template< typename U
+        nsel_REQUIRES_T(
+            std::is_move_constructible< T>::value
+            && std::is_convertible<U&&, T>::value
+        )
+    >
+    value_type value_or( U && v ) &&
+    {
+        return has_value()
+            ? std::move( contained.value() )
+            : static_cast<T>( std::forward<U>( v ) );
+    }
+
+#if nsel_P2505R >= 4
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_copy_constructible< E >::value
+            && std::is_convertible< G, E >::value
+        )
+    >
+    nsel_constexpr error_type error_or( G && e ) const &
+    {
+        return has_value()
+            ? static_cast< E >( std::forward< G >( e ) )
+            : contained.error();
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_move_constructible< E >::value
+            && std::is_convertible< G, E >::value
+        )
+    >
+    nsel_constexpr14 error_type error_or( G && e ) &&
+    {
+        return has_value()
+            ? static_cast< E >( std::forward< G >( e ) )
+            : std::move( contained.error() );
+    }
+#endif // nsel_P2505R >= 4
+
+#if nsel_P2505R >= 3
+    // Monadic operations (P2505)
+    template< typename F
+        nsel_REQUIRES_T(
+            detail::is_expected < detail::invoke_result_nocvref_t< F, value_type & > > ::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F, value_type & >::error_type, error_type >::value
+            && std::is_constructible< error_type, error_type & >::value
+        )
+    >
+    nsel_constexpr14 detail::invoke_result_nocvref_t< F, value_type & > and_then( F && f ) &
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, value_type & >( detail::invoke( std::forward< F >( f ), value() ) )
+            : detail::invoke_result_nocvref_t< F, value_type & >( unexpect, error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, const value_type & > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F, const value_type & >::error_type, error_type >::value
+            && std::is_constructible< error_type, const error_type & >::value
+        )
+    >
+    nsel_constexpr detail::invoke_result_nocvref_t< F, const value_type & > and_then( F && f ) const &
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, const value_type & >( detail::invoke( std::forward< F >( f ), value() ) )
+            : detail::invoke_result_nocvref_t< F, const value_type & >( unexpect, error() );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, value_type && > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F, value_type && >::error_type, error_type >::value
+            && std::is_constructible< error_type, error_type && >::value
+        )
+    >
+    nsel_constexpr14 detail::invoke_result_nocvref_t< F, value_type && > and_then( F && f ) &&
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, value_type && >( detail::invoke( std::forward< F >( f ), std::move( value() ) ) )
+            : detail::invoke_result_nocvref_t< F, value_type && >( unexpect, std::move( error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, const value_type && > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F, const value_type & >::error_type, error_type >::value
+            && std::is_constructible< error_type, const error_type && >::value
+        )
+    >
+    nsel_constexpr detail::invoke_result_nocvref_t< F, const value_type && > and_then( F && f ) const &&
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, const value_type && >( detail::invoke( std::forward< F >( f ), std::move( value() ) ) )
+            : detail::invoke_result_nocvref_t< F, const value_type && >( unexpect, std::move( error() ) );
+    }
+#endif
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, error_type & > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F, error_type & >::value_type, value_type >::value
+            && std::is_constructible< value_type, value_type & >::value
+        )
+    >
+    nsel_constexpr14 detail::invoke_result_nocvref_t< F, error_type & > or_else( F && f ) &
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, error_type & >( value() )
+            : detail::invoke_result_nocvref_t< F, error_type & >( detail::invoke( std::forward< F >( f ), error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, const error_type & > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F, const error_type & >::value_type, value_type >::value
+            && std::is_constructible< value_type, const value_type & >::value
+        )
+    >
+    nsel_constexpr detail::invoke_result_nocvref_t< F, const error_type & > or_else( F && f ) const &
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, const error_type & >( value() )
+            : detail::invoke_result_nocvref_t< F, const error_type & >( detail::invoke( std::forward< F >( f ), error() ) );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, error_type && > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F, error_type && >::value_type, value_type >::value
+            && std::is_constructible< value_type, value_type && >::value
+        )
+    >
+    nsel_constexpr14 detail::invoke_result_nocvref_t< F, error_type && > or_else( F && f ) &&
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, error_type && >( std::move( value() ) )
+            : detail::invoke_result_nocvref_t< F, error_type && >( detail::invoke( std::forward< F >( f ), std::move( error() ) ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, const error_type && > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F, const error_type && >::value_type, value_type >::value
+            && std::is_constructible< value_type, const value_type && >::value
+        )
+    >
+    nsel_constexpr detail::invoke_result_nocvref_t< F, const error_type && > or_else( F && f ) const &&
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, const error_type && >( std::move( value() ) )
+            : detail::invoke_result_nocvref_t< F, const error_type && >( detail::invoke( std::forward< F >( f ), std::move( error() ) ) );
+    }
+#endif
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, error_type & >::value
+            && !std::is_void< detail::transform_invoke_result_t< F, value_type & > >::value
+            && detail::valid_expected_value_type< detail::transform_invoke_result_t< F, value_type & > >::value
+        )
+    >
+    nsel_constexpr14 expected< detail::transform_invoke_result_t< F, value_type & >, error_type > transform( F && f ) &
+    {
+        return has_value()
+            ? expected< detail::transform_invoke_result_t< F, value_type & >, error_type >( detail::invoke( std::forward< F >( f ), **this ) )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, error_type & >::value
+            && std::is_void< detail::transform_invoke_result_t< F, value_type & > >::value
+        )
+    >
+    nsel_constexpr14 expected< void, error_type > transform( F && f ) &
+    {
+        return has_value()
+            ? ( detail::invoke( std::forward< F >( f ), **this ), expected< void, error_type >() )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, const error_type & >::value
+            && !std::is_void< detail::transform_invoke_result_t< F, const value_type & > >::value
+            && detail::valid_expected_value_type< detail::transform_invoke_result_t< F, const value_type & > >::value
+        )
+    >
+    nsel_constexpr expected< detail::transform_invoke_result_t< F, const value_type & >, error_type > transform( F && f ) const &
+    {
+        return has_value()
+            ? expected< detail::transform_invoke_result_t< F, const value_type & >, error_type >( detail::invoke( std::forward< F >( f ), **this ) )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, const error_type & >::value
+            && std::is_void< detail::transform_invoke_result_t< F, const value_type & > >::value
+        )
+    >
+    nsel_constexpr expected< void, error_type > transform( F && f ) const &
+    {
+        return has_value()
+            ? ( detail::invoke( std::forward< F >( f ), **this ), expected< void, error_type >() )
+            : make_unexpected( error() );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, error_type && >::value
+            && !std::is_void< detail::transform_invoke_result_t< F, value_type && > >::value
+            && detail::valid_expected_value_type< detail::transform_invoke_result_t< F, value_type && > >::value
+        )
+    >
+    nsel_constexpr14 expected< detail::transform_invoke_result_t< F, value_type && >, error_type > transform( F && f ) &&
+    {
+        return has_value()
+            ? expected< detail::transform_invoke_result_t< F, value_type && >, error_type >( detail::invoke( std::forward< F >( f ), std::move( **this ) ) )
+            : make_unexpected( std::move( error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, error_type && >::value
+            && std::is_void< detail::transform_invoke_result_t< F, value_type && > >::value
+        )
+    >
+    nsel_constexpr14 expected< void, error_type > transform( F && f ) &&
+    {
+        return has_value()
+            ? ( detail::invoke( std::forward< F >( f ), **this ), expected< void, error_type >() )
+            : make_unexpected( std::move( error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, const error_type && >::value
+            && !std::is_void< detail::transform_invoke_result_t< F, const value_type && > >::value
+            && detail::valid_expected_value_type< detail::transform_invoke_result_t< F, const value_type && > >::value
+        )
+    >
+    nsel_constexpr expected< detail::transform_invoke_result_t< F, const value_type && >, error_type > transform( F && f ) const &&
+    {
+        return has_value()
+            ? expected< detail::transform_invoke_result_t< F, const value_type && >, error_type >( detail::invoke( std::forward< F >( f ), std::move( **this ) ) )
+            : make_unexpected( std::move( error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, const error_type && >::value
+            && std::is_void< detail::transform_invoke_result_t< F, const value_type && > >::value
+        )
+    >
+    nsel_constexpr expected< void, error_type > transform( F && f ) const &&
+    {
+        return has_value()
+            ? ( detail::invoke( std::forward< F >( f ), **this ), expected< void, error_type >() )
+            : make_unexpected( std::move( error() ) );
+    }
+#endif
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::valid_unexpected_type< detail::transform_invoke_result_t< F, error_type & > >::value
+            && std::is_constructible< value_type, value_type & >::value
+        )
+    >
+    nsel_constexpr14 expected< value_type, detail::transform_invoke_result_t< F, error_type & > > transform_error( F && f ) &
+    {
+        return has_value()
+            ? expected< value_type, detail::transform_invoke_result_t< F, error_type & > >( in_place, **this )
+            : make_unexpected( detail::invoke( std::forward< F >( f ), error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::valid_unexpected_type< detail::transform_invoke_result_t< F, const error_type & > >::value
+            && std::is_constructible< value_type, const value_type & >::value
+        )
+    >
+    nsel_constexpr expected< value_type, detail::transform_invoke_result_t< F, const error_type & > > transform_error( F && f ) const &
+    {
+        return has_value()
+            ? expected< value_type, detail::transform_invoke_result_t< F, const error_type & > >( in_place, **this )
+            : make_unexpected( detail::invoke( std::forward< F >( f ), error() ) );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::valid_unexpected_type< detail::transform_invoke_result_t< F, error_type && > >::value
+            && std::is_constructible< value_type, value_type && >::value
+        )
+    >
+    nsel_constexpr14 expected< value_type, detail::transform_invoke_result_t< F, error_type && > > transform_error( F && f ) &&
+    {
+        return has_value()
+            ? expected< value_type, detail::transform_invoke_result_t< F, error_type && > >( in_place, std::move( **this ) )
+            : make_unexpected( detail::invoke( std::forward< F >( f ), std::move( error() ) ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::valid_unexpected_type< detail::transform_invoke_result_t< F, const error_type && > >::value
+            && std::is_constructible< value_type, const value_type && >::value
+        )
+    >
+    nsel_constexpr expected< value_type, detail::transform_invoke_result_t< F, const error_type && > > transform_error( F && f ) const &&
+    {
+        return has_value()
+            ? expected< value_type, detail::transform_invoke_result_t< F, const error_type && > >( in_place, std::move( **this ) )
+            : make_unexpected( detail::invoke( std::forward< F >( f ), std::move( error() ) ) );
+    }
+#endif
+#endif // nsel_P2505R >= 3
+    // unwrap()
+
+//  template <class U, class E>
+//  constexpr expected<U,E> expected<expected<U,E>,E>::unwrap() const&;
+
+//  template <class T, class E>
+//  constexpr expected<T,E> expected<T,E>::unwrap() const&;
+
+//  template <class U, class E>
+//  expected<U,E> expected<expected<U,E>, E>::unwrap() &&;
+
+//  template <class T, class E>
+//  template expected<T,E> expected<T,E>::unwrap() &&;
+
+    // factories
+
+//  template< typename Ex, typename F>
+//  expected<T,E> catch_exception(F&& f);
+
+//  template< typename F>
+//  expected<decltype(func(declval<T>())),E> map(F&& func) ;
+
+//  template< typename F>
+//  'see below' bind(F&& func);
+
+//  template< typename F>
+//  expected<T,E> catch_error(F&& f);
+
+//  template< typename F>
+//  'see below' then(F&& func);
+
+private:
+    detail::storage_t
+    <
+        T
+        ,E
+        , std::is_copy_constructible<T>::value && std::is_copy_constructible<E>::value
+        , std::is_move_constructible<T>::value && std::is_move_constructible<E>::value
+    >
+    contained;
+};
+
+/// class expected, void specialization
+
+template< typename E >
+class expected<void, E>
+{
+private:
+    template< typename, typename > friend class expected;
+
+public:
+    using value_type = void;
+    using error_type = E;
+    using unexpected_type = future_std::unexpected_type<E>;
+
+    // x.x.4.1 constructors
+
+    constexpr expected() noexcept
+        : contained( true )
+    {}
+
+    nsel_constexpr14 expected( expected const & other ) = default;
+    nsel_constexpr14 expected( expected &&      other ) = default;
+
+    constexpr explicit expected( future_std_lite_in_place_t(void) )
+        : contained( true )
+    {}
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            !std::is_convertible<G const &, E>::value /*=> explicit */
+        )
+    >
+    nsel_constexpr14 explicit expected( future_std::unexpected_type<G> const & error )
+        : contained( false )
+    {
+        contained.construct_error( E{ error.value() } );
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_convertible<G const &, E>::value /*=> non-explicit */
+        )
+    >
+    nsel_constexpr14 /*non-explicit*/ expected( future_std::unexpected_type<G> const & error )
+        : contained( false )
+    {
+        contained.construct_error( error.value() );
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            !std::is_convertible<G&&, E>::value /*=> explicit */
+        )
+    >
+    nsel_constexpr14 explicit expected( future_std::unexpected_type<G> && error )
+        : contained( false )
+    {
+        contained.construct_error( E{ std::move( error.value() ) } );
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_convertible<G&&, E>::value /*=> non-explicit */
+        )
+    >
+    nsel_constexpr14 /*non-explicit*/ expected( future_std::unexpected_type<G> && error )
+        : contained( false )
+    {
+        contained.construct_error( std::move( error.value() ) );
+    }
+
+    template< typename... Args
+        nsel_REQUIRES_T(
+            std::is_constructible<E, Args&&...>::value
+        )
+    >
+    nsel_constexpr14 explicit expected( unexpect_t, Args&&... args )
+        : contained( false )
+    {
+        contained.emplace_error( std::forward<Args>( args )... );
+    }
+
+    template< typename U, typename... Args
+        nsel_REQUIRES_T(
+            std::is_constructible<E, std::initializer_list<U>, Args&&...>::value
+        )
+    >
+    nsel_constexpr14 explicit expected( unexpect_t, std::initializer_list<U> il, Args&&... args )
+        : contained( false )
+    {
+        contained.emplace_error( il, std::forward<Args>( args )... );
+    }
+
+    // destructor
+
+    ~expected()
+    {
+        if ( ! has_value() )
+        {
+            contained.destruct_error();
+        }
+    }
+
+    // x.x.4.3 assignment
+
+    expected & operator=( expected const & other )
+    {
+        expected( other ).swap( *this );
+        return *this;
+    }
+
+    expected & operator=( expected && other ) noexcept
+    (
+        std::is_nothrow_move_assignable<E>::value &&
+        std::is_nothrow_move_constructible<E>::value )
+    {
+        expected( std::move( other ) ).swap( *this );
+        return *this;
+    }
+
+    void emplace()
+    {
+        expected().swap( *this );
+    }
+
+    // x.x.4.4 swap
+
+    template< typename G = E >
+    nsel_REQUIRES_R( void,
+        std17::is_swappable<G>::value
+        && std::is_move_constructible<G>::value
+    )
+    swap( expected & other ) noexcept
+    (
+        std::is_nothrow_move_constructible<E>::value && std17::is_nothrow_swappable<E&>::value
+    )
+    {
+        using std::swap;
+
+        if      ( ! bool(*this) && ! bool(other) ) { swap( contained.error(), other.contained.error() ); }
+        else if (   bool(*this) && ! bool(other) ) { contained.construct_error( std::move( other.error() ) );
+                                                     bool has_value = contained.has_value();
+                                                     bool other_has_value = other.has_value();
+                                                     other.contained.set_has_value(has_value);
+                                                     contained.set_has_value(other_has_value);
+                                                     }
+        else if ( ! bool(*this) &&   bool(other) ) { other.swap( *this ); }
+    }
+
+    // x.x.4.5 observers
+
+    constexpr explicit operator bool() const noexcept
+    {
+        return has_value();
+    }
+
+    constexpr bool has_value() const noexcept
+    {
+        return contained.has_value();
+    }
+
+    void value() const
+    {
+        if ( ! has_value() )
+        {
+            error_traits<error_type>::rethrow( contained.error() );
+        }
+    }
+
+    constexpr error_type const & error() const &
+    {
+        return assert( ! has_value() ), contained.error();
+    }
+
+    error_type & error() &
+    {
+        return assert( ! has_value() ), contained.error();
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+
+    constexpr error_type const && error() const &&
+    {
+        return std::move( ( assert( ! has_value() ), contained.error() ) );
+    }
+
+    error_type && error() &&
+    {
+        return std::move( ( assert( ! has_value() ), contained.error() ) );
+    }
+
+#endif
+
+    constexpr unexpected_type get_unexpected() const
+    {
+        return make_unexpected( contained.error() );
+    }
+
+    template< typename Ex >
+    bool has_exception() const
+    {
+        using ContainedEx = typename std::remove_reference< decltype( get_unexpected().value() ) >::type;
+        return ! has_value() && std::is_base_of< Ex, ContainedEx>::value;
+    }
+
+#if nsel_P2505R >= 4
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_copy_constructible< E >::value
+            && std::is_convertible< G, E >::value
+        )
+    >
+    nsel_constexpr error_type error_or( G && e ) const &
+    {
+        return has_value()
+            ? static_cast< E >( std::forward< G >( e ) )
+            : contained.error();
+    }
+
+    template< typename G = E
+        nsel_REQUIRES_T(
+            std::is_move_constructible< E >::value
+            && std::is_convertible< G, E >::value
+        )
+    >
+    nsel_constexpr14 error_type error_or( G && e ) &&
+    {
+        return has_value()
+            ? static_cast< E >( std::forward< G >( e ) )
+            : std::move( contained.error() );
+    }
+#endif // nsel_P2505R >= 4
+
+#if nsel_P2505R >= 3
+    // Monadic operations (P2505)
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F >::error_type, error_type >::value
+            && std::is_constructible< error_type, error_type & >::value
+        )
+    >
+    nsel_constexpr14 detail::invoke_result_nocvref_t< F > and_then( F && f ) &
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F >( detail::invoke( std::forward< F >( f ) ) )
+            : detail::invoke_result_nocvref_t< F >( unexpect, error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F >::error_type, error_type >::value
+            && std::is_constructible< error_type, const error_type & >::value
+        )
+    >
+    nsel_constexpr detail::invoke_result_nocvref_t< F > and_then( F && f ) const &
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F >( detail::invoke( std::forward< F >( f ) ) )
+            : detail::invoke_result_nocvref_t< F >( unexpect, error() );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F >::error_type, error_type >::value
+            && std::is_constructible< error_type, error_type && >::value
+        )
+    >
+    nsel_constexpr14 detail::invoke_result_nocvref_t< F > and_then( F && f ) &&
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F >( detail::invoke( std::forward< F >( f ) ) )
+            : detail::invoke_result_nocvref_t< F >( unexpect, std::move( error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F > >::value
+            && std::is_same< typename detail::invoke_result_nocvref_t< F >::error_type, error_type >::value
+            && std::is_constructible< error_type, const error_type && >::value
+        )
+    >
+    nsel_constexpr detail::invoke_result_nocvref_t< F > and_then( F && f ) const &&
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F >( detail::invoke( std::forward< F >( f ) ) )
+            : detail::invoke_result_nocvref_t< F >( unexpect, std::move( error() ) );
+    }
+#endif
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, error_type & > >::value
+            && std::is_void< typename detail::invoke_result_nocvref_t< F, error_type & >::value_type >::value
+        )
+    >
+    nsel_constexpr14 detail::invoke_result_nocvref_t< F, error_type & > or_else( F && f ) &
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, error_type & >()
+            : detail::invoke_result_nocvref_t< F, error_type & >( detail::invoke( std::forward< F >( f ), error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, const error_type & > >::value
+            && std::is_void< typename detail::invoke_result_nocvref_t< F, const error_type & >::value_type >::value
+        )
+    >
+    nsel_constexpr detail::invoke_result_nocvref_t< F, const error_type & > or_else( F && f ) const &
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, const error_type & >()
+            : detail::invoke_result_nocvref_t< F, const error_type & >( detail::invoke( std::forward< F >( f ), error() ) );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, error_type && > >::value
+            && std::is_void< typename detail::invoke_result_nocvref_t< F, error_type && >::value_type >::value
+        )
+    >
+    nsel_constexpr14 detail::invoke_result_nocvref_t< F, error_type && > or_else( F && f ) &&
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, error_type && >()
+            : detail::invoke_result_nocvref_t< F, error_type && >( detail::invoke( std::forward< F >( f ), std::move( error() ) ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::is_expected< detail::invoke_result_nocvref_t< F, const error_type && > >::value
+            && std::is_void< typename detail::invoke_result_nocvref_t< F, const error_type && >::value_type >::value
+        )
+    >
+    nsel_constexpr detail::invoke_result_nocvref_t< F, const error_type && > or_else( F && f ) const &&
+    {
+        return has_value()
+            ? detail::invoke_result_nocvref_t< F, const error_type && >()
+            : detail::invoke_result_nocvref_t< F, const error_type && >( detail::invoke( std::forward< F >( f ), std::move( error() ) ) );
+    }
+#endif
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, error_type & >::value
+            && !std::is_void< detail::transform_invoke_result_t< F > >::value
+        )
+    >
+    nsel_constexpr14 expected< detail::transform_invoke_result_t< F >, error_type > transform( F && f ) &
+    {
+        return has_value()
+            ? expected< detail::transform_invoke_result_t< F >, error_type >( detail::invoke( std::forward< F >( f ) ) )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, error_type & >::value
+            && std::is_void< detail::transform_invoke_result_t< F > >::value
+        )
+    >
+    nsel_constexpr14 expected< void, error_type > transform( F && f ) &
+    {
+        return has_value()
+            ? ( detail::invoke( std::forward< F >( f ) ), expected< void, error_type >() )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, const error_type & >::value
+            && !std::is_void< detail::transform_invoke_result_t< F > >::value
+        )
+    >
+    nsel_constexpr expected< detail::transform_invoke_result_t< F >, error_type > transform( F && f ) const &
+    {
+        return has_value()
+            ? expected< detail::transform_invoke_result_t< F >, error_type >( detail::invoke( std::forward< F >( f ) ) )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, const error_type & >::value
+            && std::is_void< detail::transform_invoke_result_t< F > >::value
+        )
+    >
+    nsel_constexpr expected< void, error_type > transform( F && f ) const &
+    {
+        return has_value()
+            ? ( detail::invoke( std::forward< F >( f ) ), expected< void, error_type >() )
+            : make_unexpected( error() );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, error_type && >::value
+            && !std::is_void< detail::transform_invoke_result_t< F > >::value
+        )
+    >
+    nsel_constexpr14 expected< detail::transform_invoke_result_t< F >, error_type > transform( F && f ) &&
+    {
+        return has_value()
+            ? expected< detail::transform_invoke_result_t< F >, error_type >( detail::invoke( std::forward< F >( f ) ) )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, error_type && >::value
+            && std::is_void< detail::transform_invoke_result_t< F > >::value
+        )
+    >
+    nsel_constexpr14 expected< void, error_type > transform( F && f ) &&
+    {
+        return has_value()
+            ? ( detail::invoke( std::forward< F >( f ) ), expected< void, error_type >() )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, const error_type && >::value
+            && !std::is_void< detail::transform_invoke_result_t< F > >::value
+        )
+    >
+    nsel_constexpr expected< detail::transform_invoke_result_t< F >, error_type > transform( F && f ) const &&
+    {
+        return has_value()
+            ? expected< detail::transform_invoke_result_t< F >, error_type >( detail::invoke( std::forward< F >( f ) ) )
+            : make_unexpected( error() );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            std::is_constructible< error_type, const error_type && >::value
+            && std::is_void< detail::transform_invoke_result_t< F > >::value
+        )
+    >
+    nsel_constexpr expected< void, error_type > transform( F && f ) const &&
+    {
+        return has_value()
+            ? ( detail::invoke( std::forward< F >( f ) ), expected< void, error_type >() )
+            : make_unexpected( error() );
+    }
+#endif
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::valid_unexpected_type< detail::transform_invoke_result_t< F, error_type & > >::value
+        )
+    >
+    nsel_constexpr14 expected< void, detail::transform_invoke_result_t< F, error_type & > > transform_error( F && f ) &
+    {
+        return has_value()
+            ? expected< void, detail::transform_invoke_result_t< F, error_type & > >()
+            : make_unexpected( detail::invoke( std::forward< F >( f ), error() ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::valid_unexpected_type< detail::transform_invoke_result_t< F, const error_type & > >::value
+        )
+    >
+    nsel_constexpr expected< void, detail::transform_invoke_result_t< F, const error_type & > > transform_error( F && f ) const &
+    {
+        return has_value()
+            ? expected< void, detail::transform_invoke_result_t< F, const error_type & > >()
+            : make_unexpected( detail::invoke( std::forward< F >( f ), error() ) );
+    }
+
+#if !nsel_COMPILER_GNUC_VERSION || nsel_COMPILER_GNUC_VERSION >= 490
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::valid_unexpected_type< detail::transform_invoke_result_t< F, error_type && > >::value
+        )
+    >
+    nsel_constexpr14 expected< void, detail::transform_invoke_result_t< F, error_type && > > transform_error( F && f ) &&
+    {
+        return has_value()
+            ? expected< void, detail::transform_invoke_result_t< F, error_type && > >()
+            : make_unexpected( detail::invoke( std::forward< F >( f ), std::move( error() ) ) );
+    }
+
+    template<typename F
+        nsel_REQUIRES_T(
+            detail::valid_unexpected_type< detail::transform_invoke_result_t< F, const error_type && > >::value
+        )
+    >
+    nsel_constexpr expected< void, detail::transform_invoke_result_t< F, const error_type && > > transform_error( F && f ) const &&
+    {
+        return has_value()
+            ? expected< void, detail::transform_invoke_result_t< F, const error_type && > >()
+            : make_unexpected( detail::invoke( std::forward< F >( f ), std::move( error() ) ) );
+    }
+#endif
+#endif // nsel_P2505R >= 3
+
+//  template constexpr 'see below' unwrap() const&;
+//
+//  template 'see below' unwrap() &&;
+
+    // factories
+
+//  template< typename Ex, typename F>
+//  expected<void,E> catch_exception(F&& f);
+//
+//  template< typename F>
+//  expected<decltype(func()), E> map(F&& func) ;
+//
+//  template< typename F>
+//  'see below' bind(F&& func) ;
+//
+//  template< typename F>
+//  expected<void,E> catch_error(F&& f);
+//
+//  template< typename F>
+//  'see below' then(F&& func);
+
+private:
+    detail::storage_t
+    <
+        void
+        , E
+        , std::is_copy_constructible<E>::value
+        , std::is_move_constructible<E>::value
+    >
+    contained;
+};
+
+// x.x.4.6 expected<>: comparison operators
+
+template< typename T1, typename E1, typename T2, typename E2
+    nsel_REQUIRES_T(
+        !std::is_void<T1>::value && !std::is_void<T2>::value
+    )
+>
+constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
+{
+    return bool(x) != bool(y) ? false : bool(x) ? *x == *y : x.error() == y.error();
+}
+
+template< typename T1, typename E1, typename T2, typename E2
+    nsel_REQUIRES_T(
+        std::is_void<T1>::value && std::is_void<T2>::value
+    )
+>
+constexpr bool operator==( expected<T1,E1> const & x, expected<T2,E2> const & y )
+{
+    return bool(x) != bool(y) ? false : bool(x) || static_cast<bool>( x.error() == y.error() );
+}
+
+template< typename T1, typename E1, typename T2, typename E2 >
+constexpr bool operator!=( expected<T1,E1> const & x, expected<T2,E2> const & y )
+{
+    return !(x == y);
+}
+
+#if nsel_P0323R <= 2
+
+template< typename T, typename E >
+constexpr bool operator<( expected<T,E> const & x, expected<T,E> const & y )
+{
+    return (!y) ? false : (!x) ? true : *x < *y;
+}
+
+template< typename T, typename E >
+constexpr bool operator>( expected<T,E> const & x, expected<T,E> const & y )
+{
+    return (y < x);
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( expected<T,E> const & x, expected<T,E> const & y )
+{
+    return !(y < x);
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( expected<T,E> const & x, expected<T,E> const & y )
+{
+    return !(x < y);
+}
+
+#endif
+
+// x.x.4.7 expected: comparison with T
+
+template< typename T1, typename E1, typename T2
+    nsel_REQUIRES_T(
+        !std::is_void<T1>::value
+    )
+>
+constexpr bool operator==( expected<T1,E1> const & x, T2 const & v )
+{
+    return bool(x) ? *x == v : false;
+}
+
+template< typename T1, typename E1, typename T2
+    nsel_REQUIRES_T(
+        !std::is_void<T1>::value
+    )
+>
+constexpr bool operator==(T2 const & v, expected<T1,E1> const & x )
+{
+    return bool(x) ? v == *x : false;
+}
+
+template< typename T1, typename E1, typename T2 >
+constexpr bool operator!=( expected<T1,E1> const & x, T2 const & v )
+{
+    return bool(x) ? *x != v : true;
+}
+
+template< typename T1, typename E1, typename T2 >
+constexpr bool operator!=( T2 const & v, expected<T1,E1> const & x )
+{
+    return bool(x) ? v != *x : true;
+}
+
+#if nsel_P0323R <= 2
+
+template< typename T, typename E >
+constexpr bool operator<( expected<T,E> const & x, T const & v )
+{
+    return bool(x) ? *x < v : true;
+}
+
+template< typename T, typename E >
+constexpr bool operator<( T const & v, expected<T,E> const & x )
+{
+    return bool(x) ? v < *x : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator>( T const & v, expected<T,E> const & x )
+{
+    return bool(x) ? *x < v : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator>( expected<T,E> const & x, T const & v )
+{
+    return bool(x) ? v < *x : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( T const & v, expected<T,E> const & x )
+{
+    return bool(x) ? ! ( *x < v ) : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( expected<T,E> const & x, T const & v )
+{
+    return bool(x) ? ! ( v < *x ) : true;
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( expected<T,E> const & x, T const & v )
+{
+    return bool(x) ? ! ( *x < v ) : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( T const & v, expected<T,E> const & x )
+{
+    return bool(x) ? ! ( v < *x ) : true;
+}
+
+#endif // nsel_P0323R
+
+// x.x.4.8 expected: comparison with unexpected_type
+
+template< typename T1, typename E1 , typename E2 >
+constexpr bool operator==( expected<T1,E1> const & x, unexpected_type<E2> const & u )
+{
+    return (!x) ? x.get_unexpected() == u : false;
+}
+
+template< typename T1, typename E1 , typename E2 >
+constexpr bool operator==( unexpected_type<E2> const & u, expected<T1,E1> const & x )
+{
+    return ( x == u );
+}
+
+template< typename T1, typename E1 , typename E2 >
+constexpr bool operator!=( expected<T1,E1> const & x, unexpected_type<E2> const & u )
+{
+    return ! ( x == u );
+}
+
+template< typename T1, typename E1 , typename E2 >
+constexpr bool operator!=( unexpected_type<E2> const & u, expected<T1,E1> const & x )
+{
+    return ! ( x == u );
+}
+
+#if nsel_P0323R <= 2
+
+template< typename T, typename E >
+constexpr bool operator<( expected<T,E> const & x, unexpected_type<E> const & u )
+{
+    return (!x) ? ( x.get_unexpected() < u ) : false;
+}
+
+template< typename T, typename E >
+constexpr bool operator<( unexpected_type<E> const & u, expected<T,E> const & x )
+{
+  return (!x) ? ( u < x.get_unexpected() ) : true ;
+}
+
+template< typename T, typename E >
+constexpr bool operator>( expected<T,E> const & x, unexpected_type<E> const & u )
+{
+    return ( u < x );
+}
+
+template< typename T, typename E >
+constexpr bool operator>( unexpected_type<E> const & u, expected<T,E> const & x )
+{
+    return ( x < u );
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( expected<T,E> const & x, unexpected_type<E> const & u )
+{
+    return ! ( u < x );
+}
+
+template< typename T, typename E >
+constexpr bool operator<=( unexpected_type<E> const & u, expected<T,E> const & x)
+{
+    return ! ( x < u );
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( expected<T,E> const & x, unexpected_type<E> const & u  )
+{
+    return ! ( u > x );
+}
+
+template< typename T, typename E >
+constexpr bool operator>=( unexpected_type<E> const & u, expected<T,E> const & x )
+{
+    return ! ( x > u );
+}
+
+#endif // nsel_P0323R
+
+/// x.x.x Specialized algorithms
+
+template< typename T, typename E
+    nsel_REQUIRES_T(
+        ( std::is_void<T>::value || std::is_move_constructible<T>::value )
+        && std::is_move_constructible<E>::value
+        && std17::is_swappable<T>::value
+        && std17::is_swappable<E>::value )
+>
+void swap( expected<T,E> & x, expected<T,E> & y ) noexcept ( noexcept ( x.swap(y) ) )
+{
+    x.swap( y );
+}
+
+#if nsel_P0323R <= 3
+
+template< typename T >
+constexpr auto make_expected( T && v ) -> expected< typename std::decay<T>::type >
+{
+    return expected< typename std::decay<T>::type >( std::forward<T>( v ) );
+}
+
+// expected<void> specialization:
+
+auto inline make_expected() -> expected<void>
+{
+    return expected<void>( in_place );
+}
+
+template< typename T >
+constexpr auto make_expected_from_current_exception() -> expected<T>
+{
+    return expected<T>( make_unexpected_from_current_exception() );
+}
+
+template< typename T >
+auto make_expected_from_exception( std::exception_ptr v ) -> expected<T>
+{
+    return expected<T>( unexpected_type<std::exception_ptr>( std::forward<std::exception_ptr>( v ) ) );
+}
+
+template< typename T, typename E >
+constexpr auto make_expected_from_error( E e ) -> expected<T, typename std::decay<E>::type>
+{
+    return expected<T, typename std::decay<E>::type>( make_unexpected( e ) );
+}
+
+template< typename F
+    nsel_REQUIRES_T( ! std::is_same<typename std::result_of<F()>::type, void>::value )
+>
+/*nsel_constexpr14*/
+auto make_expected_from_call( F f ) -> expected< typename std::result_of<F()>::type >
+{
+    try
+    {
+        return make_expected( f() );
+    }
+    catch (...)
+    {
+        return make_unexpected_from_current_exception();
+    }
+}
+
+template< typename F
+    nsel_REQUIRES_T( std::is_same<typename std::result_of<F()>::type, void>::value )
+>
+/*nsel_constexpr14*/
+auto make_expected_from_call( F f ) -> expected<void>
+{
+    try
+    {
+        f();
+        return make_expected();
+    }
+    catch (...)
+    {
+        return make_unexpected_from_current_exception();
+    }
+}
+
+#endif // nsel_P0323R
+
+} // namespace expected_lite
+
+using namespace expected_lite;
+
+// using expected_lite::expected;
+// using ...
+
+} // namespace future_std
+
+namespace std {
+
+// expected: hash support
+
+template< typename T, typename E >
+struct hash< future_std::expected<T,E> >
+{
+    using result_type = std::size_t;
+    using argument_type = future_std::expected<T,E>;
+
+    constexpr result_type operator()(argument_type const & arg) const
+    {
+        return arg ? std::hash<T>{}(*arg) : result_type{};
+    }
+};
+
+// TBD - ?? remove? see spec.
+template< typename T, typename E >
+struct hash< future_std::expected<T&,E> >
+{
+    using result_type = std::size_t;
+    using argument_type = future_std::expected<T&,E>;
+
+    constexpr result_type operator()(argument_type const & arg) const
+    {
+        return arg ? std::hash<T>{}(*arg) : result_type{};
+    }
+};
+
+// TBD - implement
+// bool(e), hash<expected<void,E>>()(e) shall evaluate to the hashing true;
+// otherwise it evaluates to an unspecified value if E is exception_ptr or
+// a combination of hashing false and hash<E>()(e.error()).
+
+template< typename E >
+struct hash< future_std::expected<void,E> >
+{
+};
+
+} // namespace std
+
+namespace future_std {
+
+// void unexpected() is deprecated && removed in C++17
+
+#if nsel_CPP17_OR_GREATER || nsel_COMPILER_MSVC_VERSION > 141
+template< typename E >
+using unexpected = unexpected_type<E>;
+#endif
+
+} // namespace future_std
+
+#undef nsel_REQUIRES
+#undef nsel_REQUIRES_0
+#undef nsel_REQUIRES_T
+
+nsel_RESTORE_WARNINGS()
+
+#endif // nsel_USES_STD_EXPECTED
+
+#endif // AIDGE_CORE_UTILS_FUTURE_STD_EXPECTED_H_
diff --git a/python_binding/backend/pybind_OperatorImpl.cpp b/python_binding/backend/pybind_OperatorImpl.cpp
index 11189f2f3c4a46b31d8e08d73bea17f27df07765..34610069079ee792ebbe4b261b57177b3bbe2997 100644
--- a/python_binding/backend/pybind_OperatorImpl.cpp
+++ b/python_binding/backend/pybind_OperatorImpl.cpp
@@ -10,11 +10,112 @@
  ********************************************************************************/
 
 #include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+#include "aidge/operator/Operator.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 
 namespace py = pybind11;
 namespace Aidge {
+
+/**
+ * @brief Trampoline class for binding
+ *
+ */
+class pyOperatorImpl: public OperatorImpl {
+public:
+    using OperatorImpl::OperatorImpl; // Inherit constructors
+
+    void forward() override {
+        PYBIND11_OVERRIDE(
+            void,
+            OperatorImpl,
+            forward,
+
+        );
+    }
+    void backward() override {
+        PYBIND11_OVERRIDE(
+            void,
+            OperatorImpl,
+            backward,
+
+        );
+    }
+    NbElts_t getNbRequiredData(const IOIndex_t inputIdx) const override {
+        PYBIND11_OVERRIDE_NAME(
+            NbElts_t,
+            OperatorImpl,
+            "get_nb_required_data",
+            getNbRequiredData,
+            inputIdx
+        );
+    }
+    NbElts_t getNbRequiredProtected(const IOIndex_t inputIdx) const override {
+        PYBIND11_OVERRIDE_NAME(
+            NbElts_t,
+            OperatorImpl,
+            "get_nb_required_protected",
+            getNbRequiredProtected,
+            inputIdx
+
+        );
+    }
+    NbElts_t getRequiredMemory(const IOIndex_t outputIdx,
+    const std::vector<DimSize_t> &inputsSize) const override {
+        PYBIND11_OVERRIDE_NAME(
+            NbElts_t,
+            OperatorImpl,
+            "get_required_memory",
+            getRequiredMemory,
+            outputIdx,
+            inputsSize
+
+        );
+    }
+    NbElts_t getNbConsumedData(const IOIndex_t inputIdx) const override {
+        PYBIND11_OVERRIDE_NAME(
+            NbElts_t,
+            OperatorImpl,
+            "get_nb_consumed_data",
+            getNbConsumedData,
+            inputIdx
+
+        );
+    }
+    NbElts_t getNbProducedData(const IOIndex_t outputIdx) const override {
+        PYBIND11_OVERRIDE_NAME(
+            NbElts_t,
+            OperatorImpl,
+            "get_nb_produced_data",
+            getNbProducedData,
+            outputIdx
+
+        );
+    }
+    void updateConsummerProducer() override {
+        PYBIND11_OVERRIDE_NAME(
+            void,
+            OperatorImpl,
+            "update_consummer_producer",
+            updateConsummerProducer,
+
+        );
+    }
+};
+
 void init_OperatorImpl(py::module& m){
-    py::class_<OperatorImpl, std::shared_ptr<OperatorImpl>>(m, "OperatorImpl");
+
+    py::class_<OperatorImpl, std::shared_ptr<OperatorImpl>, pyOperatorImpl>(m, "OperatorImpl", py::dynamic_attr())
+    .def(py::init<const Operator&>())
+    .def("forward", &OperatorImpl::forward)
+    .def("backward", &OperatorImpl::backward)
+    .def("get_nb_required_data", &OperatorImpl::getNbRequiredData)
+    .def("get_nb_required_protected", &OperatorImpl::getNbRequiredProtected)
+    .def("get_required_memory", &OperatorImpl::getRequiredMemory)
+    .def("get_nb_consumed_data", &OperatorImpl::getNbConsumedData)
+    .def("get_nb_produced_data", &OperatorImpl::getNbProducedData)
+    .def("update_consummer_producer", &OperatorImpl::updateConsummerProducer)
+    ;
 }
 }
diff --git a/python_binding/operator/pybind_Add.cpp b/python_binding/operator/pybind_Add.cpp
index ab8b4cf7b91d5eea2db5245a8c5122ab004b4766..0b2323c5cfb660415ec3ae009beaa7aa78afca0b 100644
--- a/python_binding/operator/pybind_Add.cpp
+++ b/python_binding/operator/pybind_Add.cpp
@@ -20,7 +20,9 @@ namespace py = pybind11;
 namespace Aidge {
 
 template <std::size_t NUM> void declare_Add(py::module &m) {
-  py::class_<Add_Op<NUM>, std::shared_ptr<Add_Op<NUM>>, Operator>(m, "Add_Op", py::multiple_inheritance());
+  py::class_<Add_Op<NUM>, std::shared_ptr<Add_Op<NUM>>, Operator>(m, "AddOp", py::multiple_inheritance())
+  .def("get_inputs_name", &Add_Op<NUM>::getInputsName)
+  .def("get_outputs_name", &Add_Op<NUM>::getOutputsName);
 
   m.def("Add", &Add<NUM>, py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_AvgPooling.cpp b/python_binding/operator/pybind_AvgPooling.cpp
index 5820e94c5cbd24150a4e81b0db34328ac35e1bf5..fe67fcb7a26f6ea1f05577b47444df5cb271110a 100644
--- a/python_binding/operator/pybind_AvgPooling.cpp
+++ b/python_binding/operator/pybind_AvgPooling.cpp
@@ -8,7 +8,7 @@
  * SPDX-License-Identifier: EPL-2.0
  *
  ********************************************************************************/
-#ifdef PYBIND
+
 #include <pybind11/pybind11.h>
 #include <pybind11/stl.h>
 
@@ -32,34 +32,21 @@ template <DimIdx_t DIM> void declare_AvgPoolingOp(py::module &m) {
   .def(py::init<const std::array<DimSize_t, DIM> &,
                 const std::array<DimSize_t, DIM> &>(),
         py::arg("kernel_dims"),
-        py::arg("stride_dims"));
-  
-  m.def(("AvgPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims, 
+        py::arg("stride_dims"))
+  .def("get_inputs_name", &AvgPooling_Op<DIM>::getInputsName)
+  .def("get_outputs_name", &AvgPooling_Op<DIM>::getOutputsName);
+
+  m.def(("AvgPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
                                                                   const std::string& name,
                                                                   const std::vector<DimSize_t> &stride_dims) {
-        // Lambda function wrapper because PyBind fails to convert const array.
-        // So we use a vector that we convert in this function to a const DimeSize_t [DIM] array. 
-        if (kernel_dims.size() != DIM) {
-            throw std::runtime_error("kernel_dims size [" + std::to_string(kernel_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        if (stride_dims.size() != DIM) {
-            throw std::runtime_error("stride_dims size [" + std::to_string(stride_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        DimSize_t tmp_kernel_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_kernel_dims_array[i] = kernel_dims[i];
-        }
-        DimSize_t tmp_stride_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_stride_dims_array[i] = stride_dims[i];
-        }
-        const DimSize_t (&kernel_dims_array)[DIM] = tmp_kernel_dims_array;
-        const DimSize_t (&stride_dims_array)[DIM] = tmp_stride_dims_array;
-        return AvgPooling<DIM>(to_array(kernel_dims_array), name, to_array(stride_dims_array));
+        AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
+        AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
+
+        return AvgPooling<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()));
     }, py::arg("kernel_dims"),
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1));
-  
+
 }
 
 
@@ -67,10 +54,9 @@ void init_AvgPooling(py::module &m) {
   declare_AvgPoolingOp<1>(m);
   declare_AvgPoolingOp<2>(m);
   declare_AvgPoolingOp<3>(m);
- 
+
   // FIXME:
   // m.def("AvgPooling1D", static_cast<NodeAPI(*)(const char*, int, int, int const
   // (&)[1])>(&AvgPooling));
 }
 } // namespace Aidge
-#endif
\ No newline at end of file
diff --git a/python_binding/operator/pybind_BatchNorm.cpp b/python_binding/operator/pybind_BatchNorm.cpp
index f43381fecc689a292e166c4da40ea0cb4842c9e6..cabaa2edd7053718160fa5013492d1914ee4cf16 100644
--- a/python_binding/operator/pybind_BatchNorm.cpp
+++ b/python_binding/operator/pybind_BatchNorm.cpp
@@ -21,7 +21,9 @@ namespace Aidge {
 
 template <DimSize_t DIM>
 void declare_BatchNormOp(py::module& m) {
-    py::class_<BatchNorm_Op<DIM>, std::shared_ptr<BatchNorm_Op<DIM>>, Operator, Attributes>(m, ("BatchNorm_Op" + std::to_string(DIM) + "D").c_str(), py::multiple_inheritance());
+    py::class_<BatchNorm_Op<DIM>, std::shared_ptr<BatchNorm_Op<DIM>>, Operator, Attributes>(m, ("BatchNormOp" + std::to_string(DIM) + "D").c_str(), py::multiple_inheritance())
+    .def("get_inputs_name", &BatchNorm_Op<DIM>::getInputsName)
+    .def("get_outputs_name", &BatchNorm_Op<DIM>::getOutputsName);
 
     m.def(("BatchNorm" + std::to_string(DIM) + "D").c_str(), &BatchNorm<DIM>, py::arg("epsilon") = 1.0e-5F, py::arg("momentum") = 0.1F, py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_Conv.cpp b/python_binding/operator/pybind_Conv.cpp
index 91ede7b6a289f3def2a9c8261ff04d2ab9836cdd..f4f7946c6ecc180f83e4bf58eee16102752f0c6e 100644
--- a/python_binding/operator/pybind_Conv.cpp
+++ b/python_binding/operator/pybind_Conv.cpp
@@ -11,7 +11,7 @@
 
 #include <pybind11/pybind11.h>
 #include <pybind11/stl.h>
-
+#include <iostream>
 #include <string>
 #include <vector>
 #include <array>
@@ -37,48 +37,28 @@ template <DimIdx_t DIM> void declare_ConvOp(py::module &m) {
         py::arg("out_channels"),
         py::arg("kernel_dims"),
         py::arg("stride_dims"),
-        py::arg("dilation_dims"));
-  
+        py::arg("dilation_dims"))
+    .def("get_inputs_name", &Conv_Op<DIM>::getInputsName)
+    .def("get_outputs_name", &Conv_Op<DIM>::getOutputsName)
+    ;
+
   m.def(("Conv" + std::to_string(DIM) + "D").c_str(), [](DimSize_t in_channels,
                                                          DimSize_t out_channels,
                                                          const std::vector<DimSize_t>& kernel_dims,
-                                                         const std::string& name, 
+                                                         const std::string& name,
                                                          const std::vector<DimSize_t> &stride_dims,
                                                          const std::vector<DimSize_t> &dilation_dims) {
-        // Lambda function wrapper because PyBind fails to convert const array.
-        // So we use a vector that we convert in this function to a const DimeSize_t [DIM] array. 
-        if (kernel_dims.size() != DIM) {
-            throw std::runtime_error("kernel_dims size [" + std::to_string(kernel_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        if (stride_dims.size() != DIM) {
-            throw std::runtime_error("stride_dims size [" + std::to_string(stride_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        if (dilation_dims.size() != DIM) {
-            throw std::runtime_error("dilation_dims size [" + std::to_string(dilation_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        DimSize_t tmp_kernel_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_kernel_dims_array[i] = kernel_dims[i];
-        }
-        DimSize_t tmp_stride_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_stride_dims_array[i] = stride_dims[i];
-        }
-        DimSize_t tmp_dilation_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_dilation_dims_array[i] = dilation_dims[i];
-        }
-        const DimSize_t (&kernel_dims_array)[DIM] = tmp_kernel_dims_array;
-        const DimSize_t (&stride_dims_array)[DIM] = tmp_stride_dims_array;
-        const DimSize_t (&dilation_dims_array)[DIM] = tmp_dilation_dims_array;
-        return Conv<DIM>(in_channels, out_channels, to_array(kernel_dims_array), name, to_array(stride_dims_array), to_array(dilation_dims_array));
+        AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
+        AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
+        AIDGE_ASSERT(dilation_dims.size() == DIM, "dilation_dims size [%ld] does not match DIM [%d]", dilation_dims.size(), DIM);
+
+        return Conv<DIM>(in_channels, out_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<DIM>(dilation_dims.begin()));
     }, py::arg("in_channels"),
        py::arg("out_channels"),
        py::arg("kernel_dims"),
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
        py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
-  
 }
 
 
@@ -86,7 +66,7 @@ void init_Conv(py::module &m) {
   declare_ConvOp<1>(m);
   declare_ConvOp<2>(m);
   declare_ConvOp<3>(m);
- 
+
   // FIXME:
   // m.def("Conv1D", static_cast<NodeAPI(*)(const char*, int, int, int const
   // (&)[1])>(&Conv));
diff --git a/python_binding/operator/pybind_ConvDepthWise.cpp b/python_binding/operator/pybind_ConvDepthWise.cpp
index 446bcdcceb3ba805223fc22e6fc19a22dcf354ec..4745ef345264763f1a890d566235be072c8e50d8 100644
--- a/python_binding/operator/pybind_ConvDepthWise.cpp
+++ b/python_binding/operator/pybind_ConvDepthWise.cpp
@@ -34,44 +34,24 @@ template <DimIdx_t DIM> void declare_ConvDepthWiseOp(py::module &m) {
                 const std::array<DimSize_t, DIM> &>(),
         py::arg("kernel_dims"),
         py::arg("stride_dims"),
-        py::arg("dilation_dims"));
-  
-  m.def(("ConvDepthWise" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims, 
+        py::arg("dilation_dims"))
+  .def("get_inputs_name", &ConvDepthWise_Op<DIM>::getInputsName)
+  .def("get_outputs_name", &ConvDepthWise_Op<DIM>::getOutputsName);
+
+  m.def(("ConvDepthWise" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
                                                                   const std::string& name,
                                                                   const std::vector<DimSize_t> &stride_dims,
                                                                   const std::vector<DimSize_t> &dilation_dims) {
-        // Lambda function wrapper because PyBind fails to convert const array.
-        // So we use a vector that we convert in this function to a const DimeSize_t [DIM] array. 
-        if (kernel_dims.size() != DIM) {
-            throw std::runtime_error("kernel_dims size [" + std::to_string(kernel_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        if (stride_dims.size() != DIM) {
-            throw std::runtime_error("stride_dims size [" + std::to_string(stride_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        if (dilation_dims.size() != DIM) {
-            throw std::runtime_error("dilation_dims size [" + std::to_string(dilation_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        DimSize_t tmp_kernel_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_kernel_dims_array[i] = kernel_dims[i];
-        }
-        DimSize_t tmp_stride_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_stride_dims_array[i] = stride_dims[i];
-        }
-        DimSize_t tmp_dilation_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_dilation_dims_array[i] = dilation_dims[i];
-        }
-        const DimSize_t (&kernel_dims_array)[DIM] = tmp_kernel_dims_array;
-        const DimSize_t (&stride_dims_array)[DIM] = tmp_stride_dims_array;
-        const DimSize_t (&dilation_dims_array)[DIM] = tmp_dilation_dims_array;
-        return ConvDepthWise<DIM>(to_array(kernel_dims_array), name, to_array(stride_dims_array), to_array(dilation_dims_array));
+        AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
+        AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
+        AIDGE_ASSERT(dilation_dims.size() == DIM, "dilation_dims size [%ld] does not match DIM [%d]", dilation_dims.size(), DIM);
+
+        return ConvDepthWise<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<DIM>(dilation_dims.begin()));
     }, py::arg("kernel_dims"),
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
        py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
-  
+
 }
 
 
@@ -79,7 +59,7 @@ void init_ConvDepthWise(py::module &m) {
   declare_ConvDepthWiseOp<1>(m);
   declare_ConvDepthWiseOp<2>(m);
   declare_ConvDepthWiseOp<3>(m);
- 
+
   // FIXME:
   // m.def("ConvDepthWise1D", static_cast<NodeAPI(*)(const char*, int, int, int const
   // (&)[1])>(&ConvDepthWise));
diff --git a/python_binding/operator/pybind_FC.cpp b/python_binding/operator/pybind_FC.cpp
index 4b9d61d082ebed4d426b41efa071d3943f83d231..c6a1c70000e3e6d604a6652716667efa1c18e956 100644
--- a/python_binding/operator/pybind_FC.cpp
+++ b/python_binding/operator/pybind_FC.cpp
@@ -20,7 +20,9 @@ namespace py = pybind11;
 namespace Aidge {
 
 void declare_FC(py::module &m) {
-  py::class_<FC_Op, std::shared_ptr<FC_Op>, Operator, Attributes>(m, "FC_Op", py::multiple_inheritance());
+  py::class_<FC_Op, std::shared_ptr<FC_Op>, Operator, Attributes>(m, "FCOp", py::multiple_inheritance())
+  .def("get_inputs_name", &FC_Op::getInputsName)
+  .def("get_outputs_name", &FC_Op::getOutputsName);
 
   m.def("FC", &FC, py::arg("out_channels"), py::arg("nobias") = false, py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_GenericOperator.cpp b/python_binding/operator/pybind_GenericOperator.cpp
index 4cf4dae2234900722058d6555582c5b78900ab7d..241fc7f4a003f53de15a42859b078c54cc98b63a 100644
--- a/python_binding/operator/pybind_GenericOperator.cpp
+++ b/python_binding/operator/pybind_GenericOperator.cpp
@@ -27,7 +27,7 @@ void init_GenericOperator(py::module& m) {
     .def("compute_output_dims", &GenericOperator_Op::computeOutputDims)
     .def("set_compute_output_dims", &GenericOperator_Op::setComputeOutputDims, py::arg("computation_function"));
 
-    m.def("GenericOperator", &GenericOperator, py::arg("type"), py::arg("nbDataIn"), py::arg("nbIn"), py::arg("nbOut"),
+    m.def("GenericOperator", &GenericOperator, py::arg("type"), py::arg("nb_data_in"), py::arg("nb_in"), py::arg("nb_out"),
           py::arg("name") = "");
 }
 }  // namespace Aidge
diff --git a/python_binding/operator/pybind_LeakyReLU.cpp b/python_binding/operator/pybind_LeakyReLU.cpp
index cae8a88bab7b59189dfbc6528cd653f1c97cb73a..af7689f0e64dd4ca8f798dcb34ea968972ace464 100644
--- a/python_binding/operator/pybind_LeakyReLU.cpp
+++ b/python_binding/operator/pybind_LeakyReLU.cpp
@@ -18,7 +18,9 @@ namespace py = pybind11;
 namespace Aidge {
 
 void init_LeakyReLU(py::module& m) {
-    py::class_<LeakyReLU_Op, std::shared_ptr<LeakyReLU_Op>, Operator, Attributes>(m, "LeakyReLU_Op", py::multiple_inheritance());
+    py::class_<LeakyReLU_Op, std::shared_ptr<LeakyReLU_Op>, Operator, Attributes>(m, "LeakyReLUOp", py::multiple_inheritance())
+    .def("get_inputs_name", &LeakyReLU_Op::getInputsName)
+    .def("get_outputs_name", &LeakyReLU_Op::getOutputsName);
 
     m.def("LeakyReLU", &LeakyReLU, py::arg("negative_slope") = 0.0f, py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_Matmul.cpp b/python_binding/operator/pybind_Matmul.cpp
index 2f738550041bcdb1ae809d68fa24fdf5a72e9164..fdb51b24a87ce358c1e7808873ebc569ca2227c8 100644
--- a/python_binding/operator/pybind_Matmul.cpp
+++ b/python_binding/operator/pybind_Matmul.cpp
@@ -20,7 +20,9 @@ namespace py = pybind11;
 namespace Aidge {
 
 void declare_MatMul(py::module &m) {
-  py::class_<MatMul_Op, std::shared_ptr<MatMul_Op>, Operator, Attributes>(m, "MatMul_Op", py::multiple_inheritance());
+  py::class_<MatMul_Op, std::shared_ptr<MatMul_Op>, Operator, Attributes>(m, "MatMulOp", py::multiple_inheritance())
+  .def("get_inputs_name", &MatMul_Op::getInputsName)
+  .def("get_outputs_name", &MatMul_Op::getOutputsName);
 
   m.def("MatMul", &MatMul, py::arg("out_channels"), py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_MaxPooling.cpp b/python_binding/operator/pybind_MaxPooling.cpp
index a930b496b49280629d71725cee79aea4d850358e..907e8cfaa6cde2451677b72beab38bd9a3938735 100644
--- a/python_binding/operator/pybind_MaxPooling.cpp
+++ b/python_binding/operator/pybind_MaxPooling.cpp
@@ -8,7 +8,7 @@
  * SPDX-License-Identifier: EPL-2.0
  *
  ********************************************************************************/
-#ifdef PYBIND
+
 #include <pybind11/pybind11.h>
 #include <pybind11/stl.h>
 
@@ -30,36 +30,27 @@ template <DimIdx_t DIM> void declare_MaxPoolingOp(py::module &m) {
     m, ("MaxPoolingOp" + std::to_string(DIM) + "D").c_str(),
     py::multiple_inheritance())
   .def(py::init<const std::array<DimSize_t, DIM> &,
-                const std::array<DimSize_t, DIM> &>(),
+                const std::array<DimSize_t, DIM> &,
+                bool>(),
         py::arg("kernel_dims"),
-        py::arg("stride_dims"));
-  
-  m.def(("MaxPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims, 
+        py::arg("stride_dims"),
+        py::arg("ceil_mode"))
+  .def("get_inputs_name", &MaxPooling_Op<DIM>::getInputsName)
+  .def("get_outputs_name", &MaxPooling_Op<DIM>::getOutputsName);
+
+  m.def(("MaxPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
                                                                   const std::string& name,
-                                                                  const std::vector<DimSize_t> &stride_dims) {
-        // Lambda function wrapper because PyBind fails to convert const array.
-        // So we use a vector that we convert in this function to a const DimeSize_t [DIM] array. 
-        if (kernel_dims.size() != DIM) {
-            throw std::runtime_error("kernel_dims size [" + std::to_string(kernel_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        if (stride_dims.size() != DIM) {
-            throw std::runtime_error("stride_dims size [" + std::to_string(stride_dims.size()) + "] does not match DIM [" + std::to_string(DIM) +"]");
-        }
-        DimSize_t tmp_kernel_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_kernel_dims_array[i] = kernel_dims[i];
-        }
-        DimSize_t tmp_stride_dims_array[DIM];
-        for (size_t i = 0; i < DIM; ++i) {
-            tmp_stride_dims_array[i] = stride_dims[i];
-        }
-        const DimSize_t (&kernel_dims_array)[DIM] = tmp_kernel_dims_array;
-        const DimSize_t (&stride_dims_array)[DIM] = tmp_stride_dims_array;
-        return MaxPooling<DIM>(to_array(kernel_dims_array), name, to_array(stride_dims_array));
+                                                                  const std::vector<DimSize_t> &stride_dims,
+                                                                  bool ceil_mode) {
+        AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
+        AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
+
+        return MaxPooling<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), ceil_mode);
     }, py::arg("kernel_dims"),
        py::arg("name") = "",
-       py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1));
-  
+       py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("ceil_mode") = false);
+
 }
 
 
@@ -67,10 +58,6 @@ void init_MaxPooling(py::module &m) {
   declare_MaxPoolingOp<1>(m);
   declare_MaxPoolingOp<2>(m);
   declare_MaxPoolingOp<3>(m);
- 
-  // FIXME:
-  // m.def("MaxPooling1D", static_cast<NodeAPI(*)(const char*, int, int, int const
-  // (&)[1])>(&MaxPooling));
+
 }
 } // namespace Aidge
-#endif
\ No newline at end of file
diff --git a/python_binding/operator/pybind_MetaOperatorDefs.cpp b/python_binding/operator/pybind_MetaOperatorDefs.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aa9f3c50e6b8c6ab9e7be46776d5fba30d775be2
--- /dev/null
+++ b/python_binding/operator/pybind_MetaOperatorDefs.cpp
@@ -0,0 +1,126 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+#include <pybind11/pybind11.h>
+#include <pybind11/stl.h>
+
+#include <string>
+#include <vector>
+#include <array>
+
+#include "aidge/backend/OperatorImpl.hpp"
+#include "aidge/operator/MetaOperatorDefs.hpp"
+#include "aidge/operator/Operator.hpp"
+#include "aidge/utils/Types.h"
+
+namespace py = pybind11;
+namespace Aidge {
+
+template <DimIdx_t DIM> void declare_PaddedConvOp(py::module &m) {
+  m.def(("PaddedConv" + std::to_string(DIM) + "D").c_str(), [](DimSize_t in_channels,
+                                                         DimSize_t out_channels,
+                                                         const std::vector<DimSize_t>& kernel_dims,
+                                                         const std::string& name,
+                                                         const std::vector<DimSize_t> &stride_dims,
+                                                         const std::vector<DimSize_t> &padding_dims,
+                                                         const std::vector<DimSize_t> &dilation_dims)
+    {
+        AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
+        AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
+        AIDGE_ASSERT(padding_dims.size() == 2*DIM, "padding_dims size [%ld] does not match DIM [%d]", padding_dims.size(), 2*DIM);
+        AIDGE_ASSERT(dilation_dims.size() == DIM, "dilation_dims size [%ld] does not match DIM [%d]", dilation_dims.size(), DIM);
+
+        return PaddedConv<DIM>(in_channels, out_channels, to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()), to_array<DIM>(dilation_dims.begin()));
+    }, py::arg("in_channels"),
+       py::arg("out_channels"),
+       py::arg("kernel_dims"),
+       py::arg("name") = "",
+       py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0),
+       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
+}
+
+template <DimIdx_t DIM> void declare_PaddedConvDepthWiseOp(py::module &m) {
+  m.def(("PaddedConvDepthWise" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
+                                                         const std::string& name,
+                                                         const std::vector<DimSize_t> &stride_dims,
+                                                         const std::vector<DimSize_t> &padding_dims,
+                                                         const std::vector<DimSize_t> &dilation_dims)
+    {
+        AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
+        AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
+        AIDGE_ASSERT(padding_dims.size() == 2*DIM, "padding_dims size [%ld] does not match DIM [%d]", padding_dims.size(), 2*DIM);
+        AIDGE_ASSERT(dilation_dims.size() == DIM, "dilation_dims size [%ld] does not match DIM [%d]", dilation_dims.size(), DIM);
+
+        return PaddedConvDepthWise<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()), to_array<DIM>(dilation_dims.begin()));
+    }, py::arg("kernel_dims"),
+       py::arg("name") = "",
+       py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0),
+       py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
+
+}
+
+template <DimIdx_t DIM> void declare_PaddedAvgPoolingOp(py::module &m) {
+  m.def(("PaddedAvgPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
+                                                         const std::string& name,
+                                                         const std::vector<DimSize_t> &stride_dims,
+                                                         const std::vector<DimSize_t> &padding_dims)
+    {
+        AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
+        AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
+        AIDGE_ASSERT(padding_dims.size() == 2*DIM, "padding_dims size [%ld] does not match DIM [%d]", padding_dims.size(), 2*DIM);
+
+        return PaddedAvgPooling<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()));
+    }, py::arg("kernel_dims"),
+       py::arg("name") = "",
+       py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0));
+
+}
+
+template <DimIdx_t DIM> void declare_PaddedMaxPoolingOp(py::module &m) {
+  m.def(("PaddedMaxPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
+                                                         const std::string& name,
+                                                         const std::vector<DimSize_t> &stride_dims,
+                                                         const std::vector<DimSize_t> &padding_dims,
+                                                         bool ceil_mode)
+    {
+        AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
+        AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
+        AIDGE_ASSERT(padding_dims.size() == 2*DIM, "padding_dims size [%ld] does not match DIM [%d]", padding_dims.size(), 2*DIM);
+
+        return PaddedMaxPooling<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()), ceil_mode);
+    }, py::arg("kernel_dims"),
+       py::arg("name") = "",
+       py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0),
+       py::arg("ceil_mode") = false);
+
+}
+
+void init_MetaOperatorDefs(py::module &m) {
+  declare_PaddedConvOp<1>(m);
+  declare_PaddedConvOp<2>(m);
+  declare_PaddedConvOp<3>(m);
+  declare_PaddedConvDepthWiseOp<1>(m);
+  declare_PaddedConvDepthWiseOp<2>(m);
+  declare_PaddedConvDepthWiseOp<3>(m);
+  declare_PaddedAvgPoolingOp<1>(m);
+  declare_PaddedAvgPoolingOp<2>(m);
+  declare_PaddedAvgPoolingOp<3>(m);
+  declare_PaddedMaxPoolingOp<1>(m);
+  declare_PaddedMaxPoolingOp<2>(m);
+  declare_PaddedMaxPoolingOp<3>(m);
+
+
+}
+} // namespace Aidge
diff --git a/python_binding/operator/pybind_Operator.cpp b/python_binding/operator/pybind_Operator.cpp
index d945b212ff6fb643302ca7512e91c7a778a39419..6b535e8cf3293b26aaa64f95ca2f9a394768935f 100644
--- a/python_binding/operator/pybind_Operator.cpp
+++ b/python_binding/operator/pybind_Operator.cpp
@@ -24,6 +24,9 @@ void init_Operator(py::module& m){
     .def("associate_input", &Operator::associateInput, py::arg("inputIdx"), py::arg("data"))
     .def("set_datatype", &Operator::setDatatype, py::arg("datatype"))
     .def("set_backend", &Operator::setBackend, py::arg("name"))
+    .def("forward", &Operator::forward)
+    // py::keep_alive forbide Python to garbage collect implementation will the Operator is not garbade collected !
+    .def("set_impl", &Operator::setImpl, py::arg("implementation"), py::keep_alive<1, 2>())
     ;
 }
 }
diff --git a/python_binding/operator/pybind_Producer.cpp b/python_binding/operator/pybind_Producer.cpp
index 1c62cd0adf6b8712073ec0674754ce7c8c2014a5..107b7ba00e4077d9f7c215257bf7fd46629481c1 100644
--- a/python_binding/operator/pybind_Producer.cpp
+++ b/python_binding/operator/pybind_Producer.cpp
@@ -35,7 +35,9 @@ void init_Producer(py::module &m) {
         "ProducerOp",
         py::multiple_inheritance())
     .def("dims", &Producer_Op::dims)
-    .def("set_output_tensor", &Producer_Op::setOutputTensor);
+    .def("set_output_tensor", &Producer_Op::setOutputTensor)
+    .def("get_inputs_name", &Producer_Op::getInputsName)
+    .def("get_outputs_name", &Producer_Op::getOutputsName);
     m.def("Producer", static_cast<std::shared_ptr<Node>(*)(const std::shared_ptr<Tensor>, const std::string&)>(&Producer), py::arg("tensor"), py::arg("name") = "");
 
     declare_Producer<1>(m);
diff --git a/python_binding/operator/pybind_ReLU.cpp b/python_binding/operator/pybind_ReLU.cpp
index 820589d76507b39ca65ac2397614aabd1221fe3e..dbcb483e8089373bc8599c2d09fed00049e2a2ac 100644
--- a/python_binding/operator/pybind_ReLU.cpp
+++ b/python_binding/operator/pybind_ReLU.cpp
@@ -18,7 +18,9 @@ namespace py = pybind11;
 namespace Aidge {
 
 void init_ReLU(py::module& m) {
-    py::class_<ReLU_Op, std::shared_ptr<ReLU_Op>, Operator>(m, "ReLU_Op", py::multiple_inheritance());
+    py::class_<ReLU_Op, std::shared_ptr<ReLU_Op>, Operator>(m, "ReLUOp", py::multiple_inheritance())
+    .def("get_inputs_name", &ReLU_Op::getInputsName)
+    .def("get_outputs_name", &ReLU_Op::getOutputsName);
 
     m.def("ReLU", &ReLU, py::arg("name") = "");
 }
diff --git a/python_binding/operator/pybind_Softmax.cpp b/python_binding/operator/pybind_Softmax.cpp
index 72ac1107181c1d7e2f578e31a965636dbb5c111b..8e50ab7c83bf43285b357cb803c0ce3eb42f4cc7 100644
--- a/python_binding/operator/pybind_Softmax.cpp
+++ b/python_binding/operator/pybind_Softmax.cpp
@@ -19,7 +19,9 @@ namespace py = pybind11;
 namespace Aidge {
 
 void init_Softmax(py::module& m) {
-    py::class_<Softmax_Op, std::shared_ptr<Softmax_Op>, Operator>(m, "Softmax_Op", py::multiple_inheritance());
+    py::class_<Softmax_Op, std::shared_ptr<Softmax_Op>, Operator>(m, "SoftmaxOp", py::multiple_inheritance())
+    .def("get_inputs_name", &Softmax_Op::getInputsName)
+    .def("get_outputs_name", &Softmax_Op::getOutputsName);
 
     m.def("Softmax", &Softmax, py::arg("name") = "");
 }
diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp
index d1287c0a928ae2ad27a839cec1c3d3955da65538..04e39b11e58718dfcc5f9faef24b140132367700 100644
--- a/python_binding/pybind_core.cpp
+++ b/python_binding/pybind_core.cpp
@@ -30,6 +30,7 @@ void init_GenericOperator(py::module&);
 void init_LeakyReLU(py::module&);
 void init_MatMul(py::module&);
 void init_MaxPooling(py::module&);
+void init_MetaOperatorDefs(py::module&);
 void init_Producer(py::module&);
 void init_ReLU(py::module&);
 void init_Softmax(py::module&);
@@ -48,14 +49,8 @@ void init_Recipies(py::module&);
 void init_Scheduler(py::module&);
 void init_TensorUtils(py::module&);
 
-void set_python_flag(){
-    // Set an env variable to know if we run with ypthon or cpp
-    py::module os_module = py::module::import("os");
-    os_module.attr("environ")["AIDGE_CORE_WITH_PYBIND"] = "1";
-}
 
 void init_Aidge(py::module& m){
-    set_python_flag();
     init_Data(m);
     init_Tensor(m);
 
@@ -77,6 +72,7 @@ void init_Aidge(py::module& m){
     init_LeakyReLU(m);
     init_MatMul(m);
     init_MaxPooling(m);
+    init_MetaOperatorDefs(m);
     init_ReLU(m);
     init_Softmax(m);
 
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..24ce15ab7ead32f98c7ac3edcd34bb2010ff4326
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+numpy
diff --git a/setup.py b/setup.py
index b88329e54feab78e39bd79be0a129030098e216a..60807df560510ad4cfacfdd2b178aca957306439 100644
--- a/setup.py
+++ b/setup.py
@@ -66,12 +66,13 @@ class CMakeBuild(build_ext):
         # used to launch setup.py to setup PythonInterp
         param_py = "-DPYTHON_EXECUTABLE=" + sys.executable
 
+        compile_type = 'Debug'
         install_path = os.path.join(sys.prefix, "lib", "libAidge")  if "AIDGE_INSTALL" not in os.environ else os.environ["AIDGE_INSTALL"]
 
-        self.spawn(['cmake', str(cwd), param_py, '-DTEST=OFF', f'-DCMAKE_INSTALL_PREFIX:PATH={install_path}'])
+        self.spawn(['cmake', str(cwd), param_py, '-DTEST=OFF', f'-DCMAKE_INSTALL_PREFIX:PATH={install_path}', f'-DCMAKE_BUILD_TYPE={compile_type}'])
         if not self.dry_run:
-            self.spawn(['cmake', '--build', '.', '--config', 'Debug', '-j', max_jobs])
-            self.spawn(['cmake', '--install', '.', '--config', 'Debug'])
+            self.spawn(['cmake', '--build', '.', '--config', compile_type, '-j', max_jobs])
+            self.spawn(['cmake', '--install', '.', '--config', compile_type])
         os.chdir(str(cwd))
 
         aidge_package = build_lib / (get_project_name())
diff --git a/src/backend/OperatorImpl.cpp b/src/backend/OperatorImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..166754cc9fe9774d922ef523ab35f569673701fd
--- /dev/null
+++ b/src/backend/OperatorImpl.cpp
@@ -0,0 +1,77 @@
+/********************************************************************************
+ * Copyright (c) 2023 CEA-List
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ ********************************************************************************/
+
+#include <cassert>
+
+#include "aidge/backend/OperatorImpl.hpp"
+#include "aidge/operator/Operator.hpp"
+#include "aidge/data/Tensor.hpp"
+#include "aidge/utils/ErrorHandling.hpp"
+
+Aidge::OperatorImpl::OperatorImpl(const Operator& op):
+    mOp(op),
+    mNbConsumedData(mOp.nbInputs(), 0),
+    mNbProducedData(mOp.nbOutputs(), 0)
+{
+    //ctor
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getNbRequiredData(const Aidge::IOIndex_t inputIdx) const {
+    assert(mOp.getInput(inputIdx) && "requires valid input");
+
+    // Requires the whole tensor by default
+    return std::static_pointer_cast<Tensor>(mOp.getInput(inputIdx))->size();
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getNbRequiredProtected(IOIndex_t inputIdx) const {
+    assert(mOp.getInput(inputIdx) && "requires valid input");
+
+    // Protect the whole tensor by default
+    return std::static_pointer_cast<Tensor>(mOp.getInput(inputIdx))->size();
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getRequiredMemory(const Aidge::IOIndex_t outputIdx,
+                                                         const std::vector<Aidge::DimSize_t> &/*inputsSize*/) const {
+    assert(mOp.getOutput(outputIdx) && "requires valid output");
+
+    // Requires the whole tensor by default, regardless of available data on inputs
+    return std::static_pointer_cast<Tensor>(mOp.getOutput(outputIdx))->size();
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getNbConsumedData(Aidge::IOIndex_t inputIdx) const {
+    assert(static_cast<std::size_t>(inputIdx) < mNbConsumedData.size());
+    return mNbConsumedData[static_cast<std::size_t>(inputIdx)];
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getNbProducedData(Aidge::IOIndex_t outputIdx) const {
+    assert(static_cast<std::size_t>(outputIdx) < mNbProducedData.size());
+    return mNbProducedData[static_cast<std::size_t>(outputIdx)];
+}
+
+void Aidge::OperatorImpl::updateConsummerProducer(){
+    // Update producer-consumer data
+    for (std::size_t inputIdx = 0; inputIdx < mNbConsumedData.size(); ++inputIdx) {
+        // each input is consumed by the minimum amount for a forward pass
+        mNbConsumedData[inputIdx] += getNbRequiredData(static_cast<IOIndex_t>(inputIdx));
+    }
+
+    for (std::size_t outputIdx = 0; outputIdx < mNbProducedData.size(); ++outputIdx) {
+        mNbProducedData[outputIdx] += getRequiredMemory(outputIdx, {});
+    }
+}
+
+void Aidge::OperatorImpl::forward() {
+    AIDGE_THROW_OR_ABORT(std::runtime_error, "forward() not implemented");
+}
+
+void Aidge::OperatorImpl::backward() {
+    AIDGE_THROW_OR_ABORT(std::runtime_error, "backward() not implemented");
+}
diff --git a/src/graphRegex/GraphRegex.cpp b/src/graphRegex/GraphRegex.cpp
index 4d2d14470a7f8ca72a3bc284231a3982de23fd1f..d8e45595d92fe3235f58c490791dde420627e9a1 100644
--- a/src/graphRegex/GraphRegex.cpp
+++ b/src/graphRegex/GraphRegex.cpp
@@ -119,6 +119,7 @@ void GraphRegex::setNodeKey(const std::string key,std::function<bool(NodePtr)> f
     if(mAllLambda.find(key) != mAllLambda.end()){
         throw std::runtime_error(key + " is define");
     }
+    mAllLambda[key] = f;
     _majConditionalInterpreterLambda();
 }
 
diff --git a/src/graphRegex/matchFsm/FsmGraph.cpp b/src/graphRegex/matchFsm/FsmGraph.cpp
index a5c9945b387d99c92f15a3ab9909aa37a6a55054..a56474e042cc44a68938b1d19e19a0c6841cb8cb 100644
--- a/src/graphRegex/matchFsm/FsmGraph.cpp
+++ b/src/graphRegex/matchFsm/FsmGraph.cpp
@@ -1,6 +1,6 @@
 #include "aidge/graphRegex/matchFsm/FsmGraph.hpp"
 
-using namespace Aidge; 
+using namespace Aidge;
 
 
 
@@ -15,7 +15,7 @@ FsmGraph::FsmGraph(const std::string query):mQuery(query){
     if(startNodes.size() != startNodesFsm.size()){
          throw std::runtime_error("bad number of Start nodes");
     }
-    
+
     std::vector<std::shared_ptr<FsmRunTimeContext>> walks;
     for(std::size_t i = 0; i < startNodes.size(); i++){
         walks.push_back(std::make_shared<FsmRunTimeContext>(startNodesFsm[i],startNodes[i]));
@@ -33,16 +33,16 @@ FsmGraph::FsmGraph(const std::string query):mQuery(query){
         for(auto fsmContext : walks){
             allContextSee.push_back(fsmContext);
             //if we are in a valid st we save it
-            //it's one solution of the posible solution of the matching 
+            //it's one solution of the posible solution of the matching
             if(fsmContext->isOnValidState()){
-                //not save 2 time the same end point 
+                //not save 2 time the same end point
                 if(!std::any_of(allValidContext.begin(), allValidContext.end(),
                     [&](std::shared_ptr<Aidge::FsmRunTimeContext> oldValid) {
                         return fsmContext->areEqual(oldValid);
                 })){
                     allValidContext.push_back(fsmContext);
                 }
-                
+
             }
 
             //dont test 2 time a fsmContext
@@ -69,7 +69,7 @@ FsmGraph::FsmGraph(const std::string query):mQuery(query){
 
 
 ///////////////
-// FSM construction 
+// FSM construction
 ///////////////
 const std::set<std::shared_ptr<FsmEdge>>& FsmGraph::getEdge(void){
     return mEdges;
@@ -141,7 +141,7 @@ void FsmGraph::mergeOneStartOneValid(const std::shared_ptr<FsmGraph> fsmGraph){
     }
 
     unionG(fsmGraph);
-    //for loop useless but for future merge it's coudl be used 
+    //for loop useless but for future merge it's coudl be used
     for(auto valid : validNodes){
         valid->unValid();
         for(auto start : startNodes){
@@ -164,9 +164,11 @@ void FsmGraph::incOriginAllNodeBy(std::size_t incr){
     for(auto node :nodes){
         node->incOrigin(incr);
     }
+    std::set<std::size_t> updatedOrigin;
     for(auto origin : mAllOrigin){
-        origin += incr;
+        updatedOrigin.insert(origin + incr);
     }
+    mAllOrigin.swap(updatedOrigin);
 }
 
 void FsmGraph::_mergeNode(std::shared_ptr<FsmNode> source,std::shared_ptr<FsmNode> dest){
@@ -194,7 +196,7 @@ void FsmGraph::_mergeNode(std::shared_ptr<FsmNode> source,std::shared_ptr<FsmNod
         }
 
     }
-    //check is source is not in graph 
+    //check is source is not in graph
     nodes = getNodes();
     if(nodes.find(source) != nodes.end() ){
         throw std::runtime_error("FsmGraph merge node not effective");
diff --git a/src/nodeTester/ConditionalInterpreter.cpp b/src/nodeTester/ConditionalInterpreter.cpp
index db2bc40ce73c4e628af0a1dfae65fef6c9351177..59515d0acd77a6202e698ca1e8f1bb28b105266c 100644
--- a/src/nodeTester/ConditionalInterpreter.cpp
+++ b/src/nodeTester/ConditionalInterpreter.cpp
@@ -1,7 +1,7 @@
 
 #include "aidge/nodeTester/ConditionalInterpreter.hpp"
 
-using namespace Aidge; 
+using namespace Aidge;
 
 
 ///////////////////////////////
@@ -63,7 +63,7 @@ using namespace Aidge;
         }catch(const std::exception& e){
             std::ostringstream errorMessage;
             errorMessage << "Error in test " << "\n\t" << e.what()  << "\n";
-            throw std::runtime_error(errorMessage.str()); 
+            throw std::runtime_error(errorMessage.str());
         }
     }
 
@@ -80,7 +80,7 @@ using namespace Aidge;
                     switch (node->getType()){
                         ///////////////////////////////////
                         //OPERATOR
-                        /////////////////////////////////// 
+                        ///////////////////////////////////
                         case ConditionalTokenTypes::NOT:
                             {
                             visit(node->getChilds(),nodeOp);
@@ -115,10 +115,10 @@ using namespace Aidge;
 
                         ///////////////////////////////////
                         //VALUE
-                        ///////////////////////////////////    
-                    
+                        ///////////////////////////////////
+
                         case ConditionalTokenTypes::KEY:
-                            
+
                             break;
                         case ConditionalTokenTypes::INTEGER:
                             {
@@ -128,7 +128,7 @@ using namespace Aidge;
                         case ConditionalTokenTypes::FLOAT:
                             {
                                 fStrToFloat(node);
-                            
+
                             }
                             break;
                         case ConditionalTokenTypes::STRING:
@@ -136,7 +136,7 @@ using namespace Aidge;
                                 fStrToStr(node);
                             }
                             break;
-                    
+
                         case ConditionalTokenTypes::NODE: //TODO
                             {
 
@@ -151,14 +151,14 @@ using namespace Aidge;
                             {
                                 visit(node->getChilds(),nodeOp);
                                 fLambda(node);
-                    
+
                             }
                             break;
 
                         case ConditionalTokenTypes::BOOL: //TODO
                             {
                             ConditionalData* data = new ConditionalData;
-                            
+
                             if(node->getValue() == "true"){
                                 data->setValue<bool>(true);
                             }else{
@@ -202,7 +202,7 @@ using namespace Aidge;
 
     void ConditionalInterpreter::fStrToFloat(const std::shared_ptr<AstNode<ConditionalTokenTypes>>& node)
     {
-       
+
         ConditionalData* data = new ConditionalData;
         data->setValue<float>(std::stof(node->getValue()));
         mResolution.push_back(data);
@@ -215,7 +215,7 @@ using namespace Aidge;
         mResolution.push_back(data);
     }
 
-    void ConditionalInterpreter::fLambda(const std::shared_ptr<AstNode<ConditionalTokenTypes>>& node) 
+    void ConditionalInterpreter::fLambda(const std::shared_ptr<AstNode<ConditionalTokenTypes>>& node)
     {
         //if the lambda have input
         ConditionalData* data;
@@ -224,7 +224,7 @@ using namespace Aidge;
         } catch (const std::exception& e) {
             std::ostringstream errorMessage;
             errorMessage << "Error in conditional interpretation when run the "<<  node->getValue() <<" Lambda\n\t" << e.what()  << "\n";
-            throw std::runtime_error(errorMessage.str()); 
+            throw std::runtime_error(errorMessage.str());
         }
 
         clearRes();
@@ -234,7 +234,7 @@ using namespace Aidge;
     void ConditionalInterpreter::fEq(void)
     {
         if (mResolution.size() != 2){
-            throw std::runtime_error("EQ need 2 arg and get :" + mResolution.size());
+            throw std::runtime_error("EQ need 2 arg and get :" + std::to_string(mResolution.size()));
         }
         auto a = mResolution[0];
         auto b = mResolution[1];
@@ -266,7 +266,7 @@ using namespace Aidge;
     void ConditionalInterpreter::fNeq(void)
     {
         if (mResolution.size() != 2){
-             throw std::runtime_error("NEQ need 2 arg and get :" + mResolution.size());
+             throw std::runtime_error("NEQ need 2 arg and get :" + std::to_string(mResolution.size()));
         }
         auto a = mResolution[0];
         auto b = mResolution[1];
@@ -295,7 +295,7 @@ using namespace Aidge;
     void ConditionalInterpreter::fAnd(void)
     {
         if (mResolution.size() != 2){
-           throw std::runtime_error("AND need 2 arg and get :" + mResolution.size());
+           throw std::runtime_error("AND need 2 arg and get :" + std::to_string(mResolution.size()));
         }
         auto a = mResolution[0];
         auto b = mResolution[1];
@@ -307,7 +307,7 @@ using namespace Aidge;
 
         ConditionalData* data = new ConditionalData;
         data->setValue<bool>( a->getValue<bool>() && b->getValue<bool>());
-        
+
 
         clearRes();
         mResolution.push_back(data);
@@ -316,7 +316,7 @@ using namespace Aidge;
     void ConditionalInterpreter::fOr(void)
     {
         if (mResolution.size() != 2){
-             throw std::runtime_error("OR need 2 arg and get :" + mResolution.size());
+             throw std::runtime_error("OR need 2 arg and get :" + std::to_string(mResolution.size()));
         }
         auto a = mResolution[0];
         auto b = mResolution[1];
@@ -328,7 +328,7 @@ using namespace Aidge;
 
         ConditionalData* data = new ConditionalData;
         data->setValue<bool>( a->getValue<bool>() || b->getValue<bool>());
-        
+
 
         clearRes();
         mResolution.push_back(data);
@@ -337,7 +337,7 @@ using namespace Aidge;
     void ConditionalInterpreter::fNot()
         {
             if (mResolution.size() != 1){
-                throw std::runtime_error("not need 1 arg and get :" + mResolution.size());
+                throw std::runtime_error("NOT need 1 arg and get :" + std::to_string(mResolution.size()));
             }
             auto a = mResolution[0];
 
@@ -347,7 +347,7 @@ using namespace Aidge;
 
             ConditionalData* data = new ConditionalData;
             data->setValue<bool>( !a->getValue<bool>() );
-            
+
             clearRes();
             mResolution.push_back(data);
 
diff --git a/unit_tests/operator/Test_MetaOperator.cpp b/unit_tests/operator/Test_MetaOperator.cpp
index e9718fc694d29713797565d3ae8c8107cc7612de..c090427914390369452ce3259f47830f01ab1754 100644
--- a/unit_tests/operator/Test_MetaOperator.cpp
+++ b/unit_tests/operator/Test_MetaOperator.cpp
@@ -20,7 +20,7 @@ using namespace Aidge;
 
 TEST_CASE("[core/operators] MetaOperator", "[Operator]") {
     SECTION("PaddedConv") {
-        auto op = PaddedConv(1, 3, {3, 3}, "padded_conv", {1, 1}, {{{1, 1}, {1, 1}}});
+        auto op = PaddedConv(1, 3, {3, 3}, "padded_conv", {1, 1}, {1, 1, 1, 1});
 
         auto microGraph = std::dynamic_pointer_cast<MetaOperator_Op>(op->getOperator())->getMicroGraph();