diff --git a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp b/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp
index a688acf1372f93b10192fec99abe0bd15de52683..d3fa06d8e6a45c0b6f7453ab091b1ce9057d0a20 100644
--- a/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp
+++ b/src/language/utils/EmbeddedIDiscreteFunctionMathFunctions.cpp
@@ -27,7 +27,7 @@
     }                                                                                                                 \
     }                                                                                                                 \
   } else {                                                                                                            \
-    throw NormalError("invalid operand type " + operand_type_name(ARG));                                              \
+    throw NormalError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(ARG));             \
   }
 
 std::shared_ptr<const IDiscreteFunction>
@@ -111,7 +111,8 @@ atan2(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<c
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g);
     throw NormalError(os.str());
   }
 }
@@ -139,7 +140,8 @@ atan2(const double a, const std::shared_ptr<const IDiscreteFunction>& f)
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(a) << " and " << operand_type_name(f);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f);
     throw NormalError(os.str());
   }
 }
@@ -167,7 +169,8 @@ atan2(const std::shared_ptr<const IDiscreteFunction>& f, const double a)
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(a);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a);
     throw NormalError(os.str());
   }
 }
@@ -253,7 +256,8 @@ pow(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<con
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g);
     throw NormalError(os.str());
   }
 }
@@ -281,7 +285,8 @@ pow(const double a, const std::shared_ptr<const IDiscreteFunction>& f)
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(a) << " and " << operand_type_name(f);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f);
     throw NormalError(os.str());
   }
 }
@@ -309,7 +314,8 @@ pow(const std::shared_ptr<const IDiscreteFunction>& f, const double a)
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(a);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a);
     throw NormalError(os.str());
   }
 }
@@ -344,7 +350,7 @@ dot(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<con
       dot(dynamic_cast<const DiscreteFunctionType&>(*f), dynamic_cast<const DiscreteFunctionType&>(*g)));
   }
   default: {
-    throw UnexpectedError("invalid data dimension " + operand_type_name(f));
+    throw UnexpectedError("invalid data dimension " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
   }
   }
 }
@@ -377,7 +383,8 @@ dot(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<con
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g);
     throw NormalError(os.str());
   }
 }
@@ -417,7 +424,8 @@ dot(const std::shared_ptr<const IDiscreteFunction>& f, const TinyVector<VectorDi
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(a);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a);
     throw NormalError(os.str());
   }
 }
@@ -457,7 +465,8 @@ dot(const TinyVector<VectorDimension>& a, const std::shared_ptr<const IDiscreteF
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(a) << " and " << operand_type_name(f);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f);
     throw NormalError(os.str());
   }
 }
@@ -502,7 +511,7 @@ min(const std::shared_ptr<const IDiscreteFunction>& f)
     }
     }
   } else {
-    throw NormalError("invalid operand type " + operand_type_name(f));
+    throw NormalError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
   }
 }
 
@@ -539,7 +548,8 @@ min(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<con
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g);
     throw NormalError(os.str());
   }
 }
@@ -567,7 +577,8 @@ min(const double a, const std::shared_ptr<const IDiscreteFunction>& f)
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(a) << " and " << operand_type_name(f);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f);
     throw NormalError(os.str());
   }
 }
@@ -595,7 +606,8 @@ min(const std::shared_ptr<const IDiscreteFunction>& f, const double a)
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(a);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a);
     throw NormalError(os.str());
   }
 }
@@ -622,7 +634,7 @@ max(const std::shared_ptr<const IDiscreteFunction>& f)
     }
     }
   } else {
-    throw NormalError("invalid operand type " + operand_type_name(f));
+    throw NormalError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
   }
 }
 
@@ -659,7 +671,8 @@ max(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<con
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g);
     throw NormalError(os.str());
   }
 }
@@ -687,7 +700,8 @@ max(const double a, const std::shared_ptr<const IDiscreteFunction>& f)
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(a) << " and " << operand_type_name(f);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f);
     throw NormalError(os.str());
   }
 }
@@ -715,7 +729,8 @@ max(const std::shared_ptr<const IDiscreteFunction>& f, const double a)
     }
   } else {
     std::stringstream os;
-    os << "incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(a);
+    os << "incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+       << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(a);
     throw NormalError(os.str());
   }
 }
@@ -743,7 +758,7 @@ sum_of(const std::shared_ptr<const IDiscreteFunction>& f)
     }
     }
   } else {
-    throw NormalError("invalid operand type " + operand_type_name(f));
+    throw NormalError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
   }
 }
 
@@ -784,7 +799,7 @@ integral_of(const std::shared_ptr<const IDiscreteFunction>& f)
     }
     }
   } else {
-    throw NormalError("invalid operand type " + operand_type_name(f));
+    throw NormalError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
   }
 }
 
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp b/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp
index 13def86d07990370bb7dce658dde09f8d06b565a..d17b53a0dcc29ad8a5cd6fc1f7774b9a9af088ba 100644
--- a/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp
+++ b/src/language/utils/EmbeddedIDiscreteFunctionOperators.cpp
@@ -15,7 +15,8 @@ invalid_operands(const LHS_T& f, const RHS_T& g)
 {
   std::ostringstream os;
   os << "undefined binary operator\n";
-  os << "note: incompatible operand types " << operand_type_name(f) << " and " << operand_type_name(g);
+  os << "note: incompatible operand types " << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) << " and "
+     << EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g);
   return os.str();
 }
 
@@ -53,7 +54,7 @@ applyUnaryOperation(const std::shared_ptr<const IDiscreteFunction>& f)
         return applyUnaryOperation<UnaryOperatorT>(fh);
       }
       default: {
-        throw UnexpectedError("invalid operand type " + operand_type_name(f));
+        throw UnexpectedError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
       }
       }
     }
@@ -73,12 +74,12 @@ applyUnaryOperation(const std::shared_ptr<const IDiscreteFunction>& f)
         return applyUnaryOperation<UnaryOperatorT>(fh);
       }
       default: {
-        throw UnexpectedError("invalid operand type " + operand_type_name(f));
+        throw UnexpectedError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
       }
       }
     }
     default: {
-      throw UnexpectedError("invalid operand type " + operand_type_name(f));
+      throw UnexpectedError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
     }
     }
     break;
@@ -90,13 +91,13 @@ applyUnaryOperation(const std::shared_ptr<const IDiscreteFunction>& f)
       return applyUnaryOperation<UnaryOperatorT>(fh);
     }
     default: {
-      throw UnexpectedError("invalid operand type " + operand_type_name(f));
+      throw UnexpectedError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
     }
     }
     break;
   }
   default: {
-    throw UnexpectedError("invalid operand type " + operand_type_name(f));
+    throw UnexpectedError("invalid operand type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
   }
   }
 }
@@ -149,7 +150,7 @@ innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
                     const std::shared_ptr<const IDiscreteFunction>& g)
 {
   Assert(f->mesh() == g->mesh());
-  Assert(isSameDiscretization(f, g));
+  Assert(EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g));
 
   switch (f->dataType()) {
   case ASTNodeDataType::double_t: {
@@ -166,7 +167,7 @@ innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
         auto gh = dynamic_cast<const DiscreteFunctionP0Vector<Dimension, double>&>(*g);
 
         if (fh.size() != gh.size()) {
-          throw NormalError(operand_type_name(f) + " spaces have different sizes");
+          throw NormalError(EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f) + " spaces have different sizes");
         }
 
         return innerCompositionLaw<BinOperatorT>(fh, gh);
@@ -226,12 +227,12 @@ innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
       return innerCompositionLaw<BinOperatorT>(fh, gh);
     }
     default: {
-      throw UnexpectedError("invalid data type " + operand_type_name(f));
+      throw UnexpectedError("invalid data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
     }
     }
   }
   default: {
-    throw UnexpectedError("invalid data type " + operand_type_name(f));
+    throw UnexpectedError("invalid data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
   }
   }
 }
@@ -246,7 +247,7 @@ innerCompositionLaw(const std::shared_ptr<const IDiscreteFunction>& f,
     throw NormalError("operands are defined on different meshes");
   }
 
-  if (not isSameDiscretization(f, g)) {
+  if (not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
     throw NormalError(invalid_operands(f, g));
   }
 
@@ -283,7 +284,7 @@ std::shared_ptr<const IDiscreteFunction>
 applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const IDiscreteFunction>& g)
 {
   Assert(fh.mesh() == g->mesh());
-  Assert(not isSameDiscretization(fh, *g));
+  Assert(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(fh, *g));
   using lhs_data_type = std::decay_t<typename DiscreteFunctionT::data_type>;
 
   switch (g->dataType()) {
@@ -342,7 +343,7 @@ applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const ID
         }
       }
       default: {
-        throw UnexpectedError("invalid rhs data type " + operand_type_name(g));
+        throw UnexpectedError("invalid rhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g));
       }
       }
     } else {
@@ -369,7 +370,7 @@ applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const ID
         return applyBinaryOperation<BinOperatorT>(fh, gh);
       }
       default: {
-        throw UnexpectedError("invalid rhs data type " + operand_type_name(g));
+        throw UnexpectedError("invalid rhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g));
       }
       }
     } else {
@@ -377,7 +378,7 @@ applyBinaryOperation(const DiscreteFunctionT& fh, const std::shared_ptr<const ID
     }
   }
   default: {
-    throw UnexpectedError("invalid rhs data type " + operand_type_name(g));
+    throw UnexpectedError("invalid rhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(g));
   }
   }
 }
@@ -388,7 +389,7 @@ applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
                      const std::shared_ptr<const IDiscreteFunction>& g)
 {
   Assert(f->mesh() == g->mesh());
-  Assert(not isSameDiscretization(f, g));
+  Assert(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g));
 
   switch (f->dataType()) {
   case ASTNodeDataType::double_t: {
@@ -415,7 +416,7 @@ applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
       return applyBinaryOperation<BinOperatorT, Dimension>(fh, g);
     }
     default: {
-      throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
+      throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
     }
     }
   }
@@ -435,7 +436,7 @@ applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
     throw NormalError("operands are defined on different meshes");
   }
 
-  Assert(not isSameDiscretization(f, g), "should call inner composition instead");
+  Assert(not EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g), "should call inner composition instead");
 
   switch (mesh->dimension()) {
   case 1: {
@@ -456,7 +457,7 @@ applyBinaryOperation(const std::shared_ptr<const IDiscreteFunction>& f,
 std::shared_ptr<const IDiscreteFunction>
 operator+(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
 {
-  if (isSameDiscretization(f, g)) {
+  if (EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
     return innerCompositionLaw<language::plus_op>(f, g);
   } else {
     throw NormalError(invalid_operands(f, g));
@@ -466,7 +467,7 @@ operator+(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_p
 std::shared_ptr<const IDiscreteFunction>
 operator-(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
 {
-  if (isSameDiscretization(f, g)) {
+  if (EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
     return innerCompositionLaw<language::minus_op>(f, g);
   } else {
     throw NormalError(invalid_operands(f, g));
@@ -476,7 +477,7 @@ operator-(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_p
 std::shared_ptr<const IDiscreteFunction>
 operator*(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
 {
-  if (isSameDiscretization(f, g)) {
+  if (EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
     return innerCompositionLaw<language::multiply_op>(f, g);
   } else {
     return applyBinaryOperation<language::multiply_op>(f, g);
@@ -486,7 +487,7 @@ operator*(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_p
 std::shared_ptr<const IDiscreteFunction>
 operator/(const std::shared_ptr<const IDiscreteFunction>& f, const std::shared_ptr<const IDiscreteFunction>& g)
 {
-  if (isSameDiscretization(f, g)) {
+  if (EmbeddedIDiscreteFunctionUtils::isSameDiscretization(f, g)) {
     return innerCompositionLaw<language::divide_op>(f, g);
   } else {
     return applyBinaryOperation<language::divide_op>(f, g);
@@ -597,7 +598,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
         }
       }
       default: {
-        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
+        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
       }
       }
     } else {
@@ -615,7 +616,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
         return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
       }
       default: {
-        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
+        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
       }
       }
     }
@@ -649,7 +650,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
         }
       }
       default: {
-        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
+        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
       }
       }
     } else {
@@ -667,7 +668,7 @@ applyBinaryOperationWithLeftConstant(const DataType& a, const std::shared_ptr<co
         return applyBinaryOperationWithLeftConstant<BinOperatorT>(a, fh);
       }
       default: {
-        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
+        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
       }
       }
     }
@@ -775,7 +776,7 @@ applyBinaryOperationWithRightConstant(const std::shared_ptr<const IDiscreteFunct
         }
       }
       default: {
-        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
+        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
       }
       }
     } else {
@@ -793,7 +794,7 @@ applyBinaryOperationWithRightConstant(const std::shared_ptr<const IDiscreteFunct
         return applyBinaryOperationWithRightConstant<BinOperatorT>(fh, a);
       }
       default: {
-        throw UnexpectedError("invalid lhs data type " + operand_type_name(f));
+        throw UnexpectedError("invalid lhs data type " + EmbeddedIDiscreteFunctionUtils::getOperandTypeName(f));
       }
       }
     }
diff --git a/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp b/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp
index 981cd8be9ca166038b5aa4b913dae63488868b64..e07a1a14d9637196eda8b09b7b63fbb77fe544ac 100644
--- a/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp
+++ b/src/language/utils/EmbeddedIDiscreteFunctionUtils.hpp
@@ -8,51 +8,53 @@
 #include <sstream>
 #include <string>
 
-template <typename T>
-PUGS_INLINE std::string
-operand_type_name(const T& t)
+struct EmbeddedIDiscreteFunctionUtils
 {
-  if constexpr (is_shared_ptr_v<T>) {
-    Assert(t.use_count() > 0);
-    return operand_type_name(*t);
-  } else if constexpr (std::is_base_of_v<IDiscreteFunction, std::decay_t<T>>) {
-    return "Vh(" + name(t.descriptor().type()) + ':' + dataTypeName(t.dataType()) + ')';
-  } else {
-    return dataTypeName(ast_node_data_type_from<T>);
+  template <typename T>
+  static PUGS_INLINE std::string
+  getOperandTypeName(const T& t)
+  {
+    if constexpr (is_shared_ptr_v<T>) {
+      Assert(t.use_count() > 0);
+      return getOperandTypeName(*t);
+    } else if constexpr (std::is_base_of_v<IDiscreteFunction, std::decay_t<T>>) {
+      return "Vh(" + name(t.descriptor().type()) + ':' + dataTypeName(t.dataType()) + ')';
+    } else {
+      return dataTypeName(ast_node_data_type_from<T>);
+    }
   }
-}
 
-PUGS_INLINE
-bool
-isSameDiscretization(const IDiscreteFunction& f, const IDiscreteFunction& g)
-{
-  if ((f.dataType() == g.dataType()) and (f.descriptor().type() == g.descriptor().type())) {
-    switch (f.dataType()) {
-    case ASTNodeDataType::double_t: {
-      return true;
-    }
-    case ASTNodeDataType::vector_t: {
-      return f.dataType().dimension() == g.dataType().dimension();
-    }
-    case ASTNodeDataType::matrix_t: {
-      return (f.dataType().numberOfRows() == g.dataType().numberOfRows()) and
-             (f.dataType().numberOfColumns() == g.dataType().numberOfColumns());
+  PUGS_INLINE
+  static bool
+  isSameDiscretization(const IDiscreteFunction& f, const IDiscreteFunction& g)
+  {
+    if ((f.dataType() == g.dataType()) and (f.descriptor().type() == g.descriptor().type())) {
+      switch (f.dataType()) {
+      case ASTNodeDataType::double_t: {
+        return true;
+      }
+      case ASTNodeDataType::vector_t: {
+        return f.dataType().dimension() == g.dataType().dimension();
+      }
+      case ASTNodeDataType::matrix_t: {
+        return (f.dataType().numberOfRows() == g.dataType().numberOfRows()) and
+               (f.dataType().numberOfColumns() == g.dataType().numberOfColumns());
+      }
+      default: {
+        throw UnexpectedError("invalid data type " + getOperandTypeName(f));
+      }
+      }
+    } else {
+      return false;
     }
-    default: {
-      throw UnexpectedError("invalid data type " + operand_type_name(f));
-    }
-    }
-  } else {
-    return false;
   }
-}
 
-PUGS_INLINE
-bool
-isSameDiscretization(const std::shared_ptr<const IDiscreteFunction>& f,
-                     const std::shared_ptr<const IDiscreteFunction>& g)
-{
-  return isSameDiscretization(*f, *g);
-}
+  static PUGS_INLINE bool
+  isSameDiscretization(const std::shared_ptr<const IDiscreteFunction>& f,
+                       const std::shared_ptr<const IDiscreteFunction>& g)
+  {
+    return isSameDiscretization(*f, *g);
+  }
+};
 
 #endif   // EMBEDDED_I_DISCRETE_FUNCTION_UTILS_HPP