diff --git a/include/aidge/data/Tensor.hpp b/include/aidge/data/Tensor.hpp
index ffee8c41a6e5adc13bad1d884e840986e7a868bb..108f1f2b4af12b3501dbb247d17052e42ebb70ed 100644
--- a/include/aidge/data/Tensor.hpp
+++ b/include/aidge/data/Tensor.hpp
@@ -57,7 +57,8 @@ class Tensor : public Data,
 
     /**
      * @brief Construct a new empty Tensor object.
-     * It has the features of an undefined scalar.
+     * It is considered undefined, i.e. dims can't be forwarded from such a Tensor.
+     * @ref undefined() method for details
      */
     Tensor(DataType dtype = DataType::Float32, DataFormat dformat = DataFormat::Default)
         : Data(Type),
@@ -65,7 +66,7 @@ class Tensor : public Data,
           mDataFormat(dformat),
           mDims(std::vector<DimSize_t>({})),
           mStrides({1}),
-          mSize(1)
+          mSize(0)
     {
         // ctor
     }
@@ -523,14 +524,30 @@ public:
     void resize(const std::vector<DimSize_t> &dims, std::vector<DimSize_t> strides = std::vector<DimSize_t>());
 
     /**
-     * @brief Return if the Tensor object has at leastone element.
-     * @return true
-     * @return false
+     * @brief Return whether the Tensor object as a rank of 0, i.e. dimensions == {}.
+     * For defined Tensors, this implies that the Tensor is scalar.
+     * For backward compatibility reasons, it is valid to call this predicate
+     * even on undefined Tensors, in which case it returns true.
+     * Hence before test the rank with this method, always check that the
+     * Tensor is not undefined().
+     * In particular for operations such as forwardDims(), one should always
+     * use undefined() to test whether the Tensor dimensions have been defined.
+     * In this case empty() can be used to distinguish scalars from N-D Tensors.
+     * @return true if rank is 0 or the tensor is undefined
      */
     bool empty() const { return mDims.empty(); }
-    // bool newempty() const noexcept {
-    //     return mSize == 0;
-    // }
+
+     /**
+     * @brief Returns whether the Tensor object is undefined.
+     * An undefined Tensor is equivalent to a tensor for which dimensions have not
+     * been defined yet. Hence, dimensions forwarding can't be done from undefined tensors.
+     * The only cases where a tensor is undefined is after the default constructor
+     * and before any call to resize().
+     * Also, as soon as the resize() method has been called, the Tensor is irreversibly defined.
+     * @ref empty() method for distinguishing an undefined from a scalar
+     * @return true if undefined
+     */
+    bool undefined() const { return mSize == 0; }
 
     /**
      * @brief Set each element of the tensor to zero.
diff --git a/include/aidge/operator/Identity.hpp b/include/aidge/operator/Identity.hpp
index 393798da2fc26b3ef3f5e4cfe54f69fd82174a5f..e07df59d888993cb33da9c20393d897ab9cf1804 100644
--- a/include/aidge/operator/Identity.hpp
+++ b/include/aidge/operator/Identity.hpp
@@ -76,7 +76,7 @@ public:
      * @return false Input has no dimensions or is a nullptr.
      */
     bool dimsForwarded() const override final {
-        return mInputs[0] ? (mInputs[0]->empty() ? false : mInputs[0]->dims() == mOutputs[0]->dims()) : false;
+        return mInputs[0] ? (mInputs[0]->undefined() ? false : mInputs[0]->dims() == mOutputs[0]->dims()) : false;
     }
 
 
diff --git a/python_binding/data/pybind_Tensor.cpp b/python_binding/data/pybind_Tensor.cpp
index 60283039b709b783484ba0b1cf821497e5bb3a8f..1d0f02a507514153621fac3dcc9681989b6f94ff 100644
--- a/python_binding/data/pybind_Tensor.cpp
+++ b/python_binding/data/pybind_Tensor.cpp
@@ -93,6 +93,7 @@ void init_Tensor(py::module& m){
     .def("get_coord", &Tensor::getCoord)
     .def("get_idx", &Tensor::getIdx)
     .def_static("get_available_backends", &Tensor::getAvailableBackends)
+    .def("undefined", &Tensor::undefined)
     .def("__str__", [](Tensor& b) {
         if (b.empty()) {
             return std::string("{}");
diff --git a/src/backend/OperatorImpl.cpp b/src/backend/OperatorImpl.cpp
index de200300a99bb33180103608238855b2f5604145..d992703fedb224e6650ce2ad50317cda3bae650f 100644
--- a/src/backend/OperatorImpl.cpp
+++ b/src/backend/OperatorImpl.cpp
@@ -29,7 +29,7 @@ Aidge::OperatorImpl::OperatorImpl(const Operator& op, const std::string& backend
 Aidge::Elts_t Aidge::OperatorImpl::getNbRequiredData(const Aidge::IOIndex_t inputIdx) const {
     if (mOp.getRawInput(inputIdx)) {
         const auto input = std::static_pointer_cast<Tensor>(mOp.getRawInput(inputIdx));
-        if (!input->empty()) {
+        if (!input->undefined()) {
             // Known amount of data: requires the whole tensor by default
             return Elts_t::DataElts(input->size());
         }
@@ -46,7 +46,7 @@ Aidge::Elts_t Aidge::OperatorImpl::getNbRequiredData(const Aidge::IOIndex_t inpu
 Aidge::Elts_t Aidge::OperatorImpl::getNbRequiredProtected(IOIndex_t inputIdx) const {
     if (mOp.getRawInput(inputIdx)) {
         const auto input = std::static_pointer_cast<Tensor>(mOp.getRawInput(inputIdx));
-        if (!input->empty()) {
+        if (!input->undefined()) {
             // Known amount of data: protect the whole tensor by default
             return Elts_t::DataElts(input->size());
         }
@@ -67,7 +67,7 @@ Aidge::Elts_t Aidge::OperatorImpl::getRequiredMemory(const Aidge::IOIndex_t outp
                                                          const std::vector<Aidge::DimSize_t> &/*inputsSize*/) const {
     if (mOp.getRawOutput(outputIdx)) {
         const auto output = std::static_pointer_cast<Tensor>(mOp.getRawOutput(outputIdx));
-        if (!output->empty()) {
+        if (!output->undefined()) {
             // Known amount of data: requires the whole tensor by default,
             // regardless of available data on inputs
             return Elts_t::DataElts(output->size());
diff --git a/src/data/Tensor.cpp b/src/data/Tensor.cpp
index 28fb90cebf8e387e69f1ec39c46a6a47c8a4d316..d1bf32594c9a79b6519613327c87370facc138ad 100644
--- a/src/data/Tensor.cpp
+++ b/src/data/Tensor.cpp
@@ -150,13 +150,12 @@ Aidge::Tensor::~Tensor() noexcept = default;
 
 void Aidge::Tensor::resize(const std::vector<Aidge::DimSize_t>& dims,
                            std::vector<Aidge::DimSize_t> strides) {
-    // TODO: scalar Tensor not handled
     if (dims.empty()) {  // scalar
         mDims = std::vector<DimSize_t>(0);
         mStrides = std::vector<DimSize_t>({1});
         mContiguous = true;
 
-        computeSize();
+        computeSize(); // will set mSize to 1
         if (mImpl) {
             mImpl->resize(mDims);
         }
@@ -214,7 +213,7 @@ void Aidge::Tensor::resize(const std::vector<Aidge::DimSize_t>& dims,
 
 std::string Aidge::Tensor::toString() const {
     AIDGE_ASSERT(
-        mImpl && (dims().empty() || (dims() == std::vector<DimSize_t>({0})) ||
+        mImpl && (undefined() || (dims() == std::vector<DimSize_t>({0})) ||
                   (mImpl->hostPtr() != nullptr)),
         "tensor should have a valid host pointer");
 
diff --git a/src/graph/GraphView.cpp b/src/graph/GraphView.cpp
index 9528e511be230cd8ac689876689f313782c9b0ab..4ec3334454034f20badb246b7030594bee0c0e48 100644
--- a/src/graph/GraphView.cpp
+++ b/src/graph/GraphView.cpp
@@ -152,7 +152,7 @@ void Aidge::GraphView::save(const std::string& path, bool verbose, bool showProd
                 // Add-on to display the operator's output dimensions
                 std::string dims = "";
                 const auto op = std::dynamic_pointer_cast<OperatorTensor>(node_ptr->getOperator());
-                if (op && !op->getOutput(outputIdx)->dims().empty()) {
+                if (op && !op->getOutput(outputIdx)->undefined()) {
                   dims += " " + fmt::format("{}", op->getOutput(outputIdx)->dims());
                 }
 
@@ -198,7 +198,7 @@ void Aidge::GraphView::save(const std::string& path, bool verbose, bool showProd
         // Add-on to display the operator's output dimensions
         std::string dims = "";
         const auto op = std::dynamic_pointer_cast<OperatorTensor>(output.first->getOperator());
-        if (op && op->getOutput(output.second) && !op->getOutput(output.second)->dims().empty()) {
+        if (op && op->getOutput(output.second) && !op->getOutput(output.second)->undefined()) {
           dims += " " + fmt::format("{}", op->getOutput(output.second)->dims());
         }
 
@@ -441,8 +441,8 @@ bool Aidge::GraphView::forwardDims(const std::vector<std::vector<Aidge::DimSize_
                 // Input is missing
                 AIDGE_ASSERT(nodePtr->getOperator()->getRawInput(i),
                   "Missing input#{} for node {} ({})", i, nodePtr->name(), nodePtr->type());
-                AIDGE_ASSERT(!std::static_pointer_cast<Tensor>(nodePtr->getOperator()->getRawInput(i))->empty(),
-                  "Empty input#{} for node {} ({})", i, nodePtr->name(), nodePtr->type());
+                AIDGE_ASSERT(!std::static_pointer_cast<Tensor>(nodePtr->getOperator()->getRawInput(i))->undefined(),
+                  "Undefined input#{} for node {} ({})", i, nodePtr->name(), nodePtr->type());
             }
 
         }
diff --git a/src/operator/Gather.cpp b/src/operator/Gather.cpp
index c28a0587a755ef0a910ec5bfdeb9caa2f1edc216..cd3c4357434ec4b49b6ea05e0d2633adfee7bfd0 100644
--- a/src/operator/Gather.cpp
+++ b/src/operator/Gather.cpp
@@ -51,7 +51,7 @@ void Aidge::Gather_OpImpl::forward() {
 const std::string Aidge::Gather_Op::Type = "Gather";
 
 bool Aidge::Gather_Op::dimsForwarded() const {
-    if (getInput(1) && !getInput(1)->empty()) {
+    if (getInput(1) && !getInput(1)->undefined()) {
         // output dims are data dependent
         return false;
     }
diff --git a/src/operator/Memorize.cpp b/src/operator/Memorize.cpp
index adf79b5c69e991ad7979184c313448e4288a8ecb..88a182f2ae7d51abb059faa64058fb701a033b56 100644
--- a/src/operator/Memorize.cpp
+++ b/src/operator/Memorize.cpp
@@ -85,12 +85,12 @@ bool Aidge::Memorize_Op::forwardDims(bool /*allowDataDependency*/) {
     if (inputsAssociated(false)) {
         // Only require one of the input to have dims defined
         // Otherwise, forwardDims() won't converge!
-        if (!(getInput(0)->empty())) {
+        if (!(getInput(0)->undefined())) {
             const auto expectedDims =  getInput(0)->dims();
             mOutputs[0]->resize(expectedDims);
             return true;
         }
-        else if (!(getInput(1)->empty())) {
+        else if (!(getInput(1)->undefined())) {
             const auto expectedDims =  getInput(1)->dims();
             mOutputs[0]->resize(expectedDims);
             return true;
@@ -105,7 +105,7 @@ bool Aidge::Memorize_Op::dimsForwarded() const {
     bool forwarded = true;
     // check outputs have been filled
     for (IOIndex_t i = 0; i < nbOutputs(); ++i) {
-        forwarded &= !(getOutput(i)->empty());
+        forwarded &= !(getOutput(i)->undefined());
     }
     return forwarded;
 }
diff --git a/src/operator/OperatorTensor.cpp b/src/operator/OperatorTensor.cpp
index 5df90020a43ad6cffebcd2345c075837f11462b1..938a386c4ca743e33f97caa981ff160522299948 100644
--- a/src/operator/OperatorTensor.cpp
+++ b/src/operator/OperatorTensor.cpp
@@ -123,7 +123,7 @@ bool Aidge::OperatorTensor::inputsAssociated(bool checkNonEmpty) const {
         }
 
         if (checkNonEmpty && getInput(i)) {
-            associated &= !(getInput(i)->empty());
+            associated &= !(getInput(i)->undefined());
         }
     }
 
@@ -152,13 +152,13 @@ bool Aidge::OperatorTensor::dimsForwarded() const {
     // check both inputs and outputs have been filled
     for (IOIndex_t i = 0; i < nbInputs(); ++i) {
         if (inputCategory(i) != InputCategory::OptionalData && inputCategory(i) != InputCategory::OptionalParam) {
-            forwarded &= mInputs[i] ? !(getInput(i)->empty()) : false;
+            forwarded &= mInputs[i] ? !(getInput(i)->undefined()) : false;
         }
     }
     for (IOIndex_t i = 0; i < nbOutputs(); ++i) {
         // If getOutput(i) is nullptr, ignore this output (it may be a dummy
         // output in a MetaOperator)
-        forwarded &= (getOutput(i)) ? !(getOutput(i)->empty()) : true;
+        forwarded &= (getOutput(i)) ? !(getOutput(i)->undefined()) : true;
     }
     return forwarded;
 }
diff --git a/src/operator/Reshape.cpp b/src/operator/Reshape.cpp
index 4184fc18abbc5490a1d6fbf7363fef817c7ecbc9..cc31eeea758853a4183569d58412c427bd32006c 100644
--- a/src/operator/Reshape.cpp
+++ b/src/operator/Reshape.cpp
@@ -31,7 +31,7 @@ void Aidge::Reshape_OpImpl::forward() {
 const std::string Aidge::Reshape_Op::Type = "Reshape";
 
 bool Aidge::Reshape_Op::dimsForwarded() const {
-    if (getInput(1) && !getInput(1)->empty()) {
+    if (getInput(1) && !getInput(1)->undefined()) {
         // output dims are data dependent
         return false;
     }
diff --git a/src/operator/Resize.cpp b/src/operator/Resize.cpp
index 966e1c3e032e64e75d3606fca022b84f9da8fbaf..0d407d4f97a17b8a89378bc83c1039423d9b2949 100644
--- a/src/operator/Resize.cpp
+++ b/src/operator/Resize.cpp
@@ -27,9 +27,9 @@ const std::string Aidge::Resize_Op::Type = "Resize";
 
 bool Aidge::Resize_Op::dimsForwarded() const {
     // in case of ROI add getInput(1) condition
-    if ((getInput(1) && !getInput(1)->empty())
-        || (getInput(2) && !getInput(2)->empty())
-        || (getInput(3) && !getInput(3)->empty())
+    if ((getInput(1) && !getInput(1)->undefined())
+        || (getInput(2) && !getInput(2)->undefined())
+        || (getInput(3) && !getInput(3)->undefined())
         )
     {
         // output dims are data dependent
@@ -44,9 +44,9 @@ bool Aidge::Resize_Op::forwardDims(bool allowDataDependency) {
         AIDGE_ASSERT(getInput(0)->nbDims() == 4,
             "input tensor must have dimensions = 4 (batch, channel, height, width).");
 
-        const bool input1ROIPresent           = getInput(1) && !getInput(1)->empty();
-        const bool input2ScalesPresent        = getInput(2) && !getInput(2)->empty();
-        const bool input3SizesPresent         = getInput(3) && !getInput(3)->empty();
+        const bool input1ROIPresent           = getInput(1) && !getInput(1)->undefined();
+        const bool input2ScalesPresent        = getInput(2) && !getInput(2)->undefined();
+        const bool input3SizesPresent         = getInput(3) && !getInput(3)->undefined();
 
         AIDGE_ASSERT(input2ScalesPresent != input3SizesPresent, "Only one of scales and  sizes can be specified.")
 
@@ -118,4 +118,4 @@ void Aidge::Resize_Op::setBackend(const std::string& name, Aidge::DeviceIdx_t de
     if(getInput(3)) {
         getInput(3)->setBackend(name, device);
     }
-}
\ No newline at end of file
+}
diff --git a/src/operator/Slice.cpp b/src/operator/Slice.cpp
index 3cc2de686435a304326e2a4a60dad6c12a50349c..4fcfd587a9b3d8858b2e8a71605743c6702cb310 100644
--- a/src/operator/Slice.cpp
+++ b/src/operator/Slice.cpp
@@ -29,10 +29,10 @@
 const std::string Aidge::Slice_Op::Type = "Slice";
 
 bool Aidge::Slice_Op::dimsForwarded() const {
-    if ((getInput(1) && !getInput(1)->empty())
-        || (getInput(2) && !getInput(2)->empty())
-        || (getInput(3) && !getInput(3)->empty())
-        || (getInput(4) && !getInput(4)->empty()))
+    if ((getInput(1) && !getInput(1)->undefined())
+        || (getInput(2) && !getInput(2)->undefined())
+        || (getInput(3) && !getInput(3)->undefined())
+        || (getInput(4) && !getInput(4)->undefined()))
     {
         // output dims are data dependent
         return false;
diff --git a/src/operator/Split.cpp b/src/operator/Split.cpp
index a0cb049b19e9411daf65bbe2a10319c62b32c1b8..31de75e410afef98843d1f59a1221ecd3ba91832 100644
--- a/src/operator/Split.cpp
+++ b/src/operator/Split.cpp
@@ -55,7 +55,7 @@ void Aidge::Split_OpImpl::forward() {
 const std::string Aidge::Split_Op::Type = "Split";
 
 bool Aidge::Split_Op::dimsForwarded() const {
-    if ((getInput(1) && !getInput(1)->empty()))
+    if ((getInput(1) && !getInput(1)->undefined()))
     {
         // output dims are data dependent
         return false;
diff --git a/unit_tests/data/Test_Tensor.cpp b/unit_tests/data/Test_Tensor.cpp
index 62e90dcbd7c20548019afae1a04f84b3e1d4484a..98d3193ffc56f78bb5274ebe0795a4d67d163d27 100644
--- a/unit_tests/data/Test_Tensor.cpp
+++ b/unit_tests/data/Test_Tensor.cpp
@@ -36,7 +36,7 @@ TEST_CASE("[core/data] Tensor(Construction)", "[Tensor][Constructor]") {
         Tensor T_default{};
         REQUIRE((
             (T_default.dataType() == DataType::Float32) &&
-            (T_default.size() == 1) &&
+            (T_default.size() == 0) &&
             (T_default.dims() == std::vector<DimSize_t>({})) &&
             (T_default.strides() == std::vector<DimSize_t>({1})) &&
             (T_default.getImpl() == nullptr) &&
diff --git a/unit_tests/operator/Test_GlobalAveragePooling_Op.cpp b/unit_tests/operator/Test_GlobalAveragePooling_Op.cpp
index d20f689aba55d8cbaef553388d4666fd6c1d7172..1d99fc7a513d0fa183fac786acee253a7cc97f10 100644
--- a/unit_tests/operator/Test_GlobalAveragePooling_Op.cpp
+++ b/unit_tests/operator/Test_GlobalAveragePooling_Op.cpp
@@ -46,9 +46,7 @@ TEST_CASE("[core/operator] GlobalAveragePooling_Op(forwardDims)",
   SECTION("Connected Inputs") {
     SECTION("empty tensor") {
       for (uint16_t trial = 0; trial < NB_TRIALS; ++trial) {
-        const std::size_t nb_dims = 0;
-        std::vector<std::size_t> dims(nb_dims);
-        input_T->resize(dims);
+        // Test that on undefined input it does not fail
         REQUIRE_NOTHROW(op->forwardDims());
       }
     }