From b7de038dd938ee8cf3501a46aa8e7ef9f107b8d5 Mon Sep 17 00:00:00 2001 From: Amit Singh Date: Tue, 15 Feb 2022 21:36:08 +0530 Subject: [PATCH] refactor(expression): use `std::invoke` and add types to functional objects to catch type errors --- .../boost/numeric/ublas/tensor/expression.hpp | 6 +- .../ublas/tensor/operators_arithmetic.hpp | 83 +++++++++++------ .../ublas/tensor/operators_comparison.hpp | 93 ++++++++++++++----- 3 files changed, 129 insertions(+), 53 deletions(-) diff --git a/include/boost/numeric/ublas/tensor/expression.hpp b/include/boost/numeric/ublas/tensor/expression.hpp index 6e70699b9..edf7c03a7 100644 --- a/include/boost/numeric/ublas/tensor/expression.hpp +++ b/include/boost/numeric/ublas/tensor/expression.hpp @@ -181,8 +181,8 @@ struct binary_tensor_expression [[nodiscard]] constexpr auto const& left_expr() const noexcept{ return cast_tensor_expression(el); } [[nodiscard]] constexpr auto const& right_expr() const noexcept{ return cast_tensor_expression(er); } - [[nodiscard]] inline - constexpr decltype(auto) operator()(size_type i) const { return op(left_expr()(i), right_expr()(i)); } + [[nodiscard]] inline constexpr + decltype(auto) operator()(size_type i) const { return std::invoke(op, left_expr()(i), right_expr()(i)); } protected: /** @@ -255,7 +255,7 @@ struct unary_tensor_expression [[nodiscard]] constexpr auto const& expr() const noexcept{ return cast_tensor_expression(e); } [[nodiscard]] inline constexpr - decltype(auto) operator()(size_type i) const { return op(expr()(i)); } + decltype(auto) operator()(size_type i) const { return std::invoke(op, expr()(i)); } protected: /** diff --git a/include/boost/numeric/ublas/tensor/operators_arithmetic.hpp b/include/boost/numeric/ublas/tensor/operators_arithmetic.hpp index fec0f42d3..6bd385870 100644 --- a/include/boost/numeric/ublas/tensor/operators_arithmetic.hpp +++ b/include/boost/numeric/ublas/tensor/operators_arithmetic.hpp @@ -76,9 +76,10 @@ inline constexpr auto operator*( EL&& lhs, ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_binary_tensor_expression( - std::forward(lhs), std::forward(rhs), std::multiplies<>{} + std::forward(lhs), std::forward(rhs), std::multiplies{} ); } @@ -91,9 +92,10 @@ inline constexpr auto operator+( EL&& lhs, ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_binary_tensor_expression( - std::forward(lhs), std::forward(rhs), std::plus<>{} + std::forward(lhs), std::forward(rhs), std::plus{} ); } @@ -106,9 +108,10 @@ inline constexpr auto operator-( EL&& lhs, ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_binary_tensor_expression( - std::forward(lhs), std::forward(rhs), std::minus<>{} + std::forward(lhs), std::forward(rhs), std::minus{} ); } @@ -121,9 +124,10 @@ inline constexpr auto operator/( EL&& lhs, ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_binary_tensor_expression( - std::forward(lhs), std::forward(rhs), std::divides<>{} + std::forward(lhs), std::forward(rhs), std::divides{} ); } @@ -136,9 +140,10 @@ inline constexpr auto operator*( EL&& lhs, ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_binary_tensor_expression( - std::forward(lhs), std::forward(rhs), std::multiplies<>{} + std::forward(lhs), std::forward(rhs), std::multiplies{} ); } @@ -151,9 +156,10 @@ inline constexpr auto operator+( EL&& lhs, ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_binary_tensor_expression( - std::forward(lhs), std::forward(rhs), std::plus<>{} + std::forward(lhs), std::forward(rhs), std::plus{} ); } @@ -166,9 +172,10 @@ inline constexpr auto operator-( EL&& lhs, ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_binary_tensor_expression( - std::forward(lhs), std::forward(rhs), std::minus<>{} + std::forward(lhs), std::forward(rhs), std::minus{} ); } @@ -180,9 +187,10 @@ template inline constexpr auto operator/( EL&& lhs, ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_binary_tensor_expression( - std::forward(lhs), std::forward(rhs), std::divides<>{} + std::forward(lhs), std::forward(rhs), std::divides{} ); } @@ -204,7 +212,7 @@ inline constexpr auto operator+( EL&& lhs, ER&& rhs ) noexcept ); return boost::numeric::ublas::detail::make_binary_tensor_expression ( - std::forward(lhs), std::forward(rhs), [](auto const& l, auto const& r){ return l + r; } + std::forward(lhs), std::forward(rhs), std::plus{} ); } @@ -227,7 +235,7 @@ inline constexpr auto operator-( EL&& lhs, ER&& rhs ) noexcept ); return boost::numeric::ublas::detail::make_binary_tensor_expression ( - std::forward(lhs), std::forward(rhs), [](auto const& l, auto const& r){ return l - r; } + std::forward(lhs), std::forward(rhs), std::minus{} ); } @@ -249,7 +257,7 @@ inline constexpr auto operator*( EL&& lhs, ER&& rhs ) noexcept ); return boost::numeric::ublas::detail::make_binary_tensor_expression ( - std::forward(lhs), std::forward(rhs), [](auto const& l, auto const& r){ return l * r; } + std::forward(lhs), std::forward(rhs), std::multiplies{} ); } @@ -271,7 +279,7 @@ inline constexpr auto operator/( EL&& lhs, ER&& rhs ) noexcept ); return boost::numeric::ublas::detail::make_binary_tensor_expression ( - std::forward(lhs), std::forward(rhs), [](auto const& l, auto const& r){ return l / r; } + std::forward(lhs), std::forward(rhs), std::divides{} ); } @@ -284,10 +292,11 @@ inline constexpr auto operator+( ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( std::forward(rhs), - [lhs](auto const& r){ return lhs + r; } + [lhs](value_type const& r){ return lhs + r; } ); } @@ -298,10 +307,11 @@ inline constexpr auto operator-( ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( std::forward(rhs), - [lhs](auto const& r){ return lhs - r; } + [lhs](value_type const& r){ return lhs - r; } ); } @@ -312,10 +322,11 @@ inline constexpr auto operator*( ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( std::forward(rhs), - [lhs](auto const& r){ return lhs * r; } + [lhs](value_type const& r){ return lhs * r; } ); } @@ -326,10 +337,11 @@ inline constexpr auto operator/( ER&& rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( std::forward(rhs), - [lhs](auto const& r){ return lhs / r; } + [lhs](value_type const& r){ return lhs / r; } ); } @@ -340,10 +352,11 @@ inline constexpr auto operator+( typename boost::numeric::ublas::detail::real_expression_type_t::value_type rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( std::forward(lhs), - [rhs] (auto const& l) { return l + rhs; } + [rhs] (value_type const& l) { return l + rhs; } ); } @@ -354,10 +367,11 @@ inline constexpr auto operator-( typename boost::numeric::ublas::detail::real_expression_type_t::value_type rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( std::forward(lhs), - [rhs] (auto const& l) { return l - rhs; } + [rhs] (value_type const& l) { return l - rhs; } ); } @@ -368,10 +382,11 @@ inline constexpr auto operator*( typename boost::numeric::ublas::detail::real_expression_type_t::value_type rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( std::forward(lhs), - [rhs] (auto const& l) { return l * rhs; } + [rhs] (value_type const& l) { return l * rhs; } ); } @@ -382,10 +397,11 @@ inline constexpr auto operator/( typename boost::numeric::ublas::detail::real_expression_type_t::value_type rhs ) noexcept { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( std::forward(lhs), - [rhs] (auto const& l) { return l / rhs; } + [rhs] (value_type const& l) { return l / rhs; } ); } @@ -394,7 +410,8 @@ inline constexpr auto& operator += ( boost::numeric::ublas::tensor_core& lhs, boost::numeric::ublas::detail::tensor_expression,D> const& expr ){ - boost::numeric::ublas::detail::eval(lhs, expr(), [](auto& l, auto const& r) { l+=r; } ); + using value_type = typename boost::numeric::ublas::tensor_core::value_type; + boost::numeric::ublas::detail::eval(lhs, expr(), [](value_type& l, value_type const& r) { l+=r; } ); return lhs; } @@ -403,7 +420,8 @@ inline constexpr auto& operator -= ( boost::numeric::ublas::tensor_core& lhs, const boost::numeric::ublas::detail::tensor_expression,D> &expr ){ - boost::numeric::ublas::detail::eval(lhs, expr(), [](auto& l, auto const& r) { l-=r; } ); + using value_type = typename boost::numeric::ublas::tensor_core::value_type; + boost::numeric::ublas::detail::eval(lhs, expr(), [](value_type& l, value_type const& r) { l-=r; } ); return lhs; } @@ -412,7 +430,8 @@ inline constexpr auto& operator *= ( boost::numeric::ublas::tensor_core& lhs, const boost::numeric::ublas::detail::tensor_expression,D> &expr ){ - boost::numeric::ublas::detail::eval(lhs, expr(), [](auto& l, auto const& r) { l*=r; } ); + using value_type = typename boost::numeric::ublas::tensor_core::value_type; + boost::numeric::ublas::detail::eval(lhs, expr(), [](value_type& l, value_type const& r) { l*=r; } ); return lhs; } @@ -421,7 +440,8 @@ inline constexpr auto& operator /= ( boost::numeric::ublas::tensor_core& lhs, const boost::numeric::ublas::detail::tensor_expression,D> &expr ){ - boost::numeric::ublas::detail::eval(lhs, expr(), [](auto& l, auto const& r) { l/=r; } ); + using value_type = typename boost::numeric::ublas::tensor_core::value_type; + boost::numeric::ublas::detail::eval(lhs, expr(), [](value_type& l, value_type const& r) { l/=r; } ); return lhs; } @@ -433,7 +453,8 @@ inline constexpr auto& operator += ( boost::numeric::ublas::tensor_core& lhs, typename boost::numeric::ublas::tensor_core::value_type r ){ - boost::numeric::ublas::detail::eval(lhs, [r](auto& l) { l+=r; } ); + using value_type = typename boost::numeric::ublas::tensor_core::value_type; + boost::numeric::ublas::detail::eval(lhs, [r](value_type& l) { l+=r; } ); return lhs; } @@ -442,7 +463,8 @@ inline constexpr auto& operator -= ( boost::numeric::ublas::tensor_core& lhs, typename boost::numeric::ublas::tensor_core::value_type r ){ - boost::numeric::ublas::detail::eval(lhs, [r](auto& l) { l-=r; } ); + using value_type = typename boost::numeric::ublas::tensor_core::value_type; + boost::numeric::ublas::detail::eval(lhs, [r](value_type& l) { l-=r; } ); return lhs; } @@ -451,7 +473,8 @@ inline constexpr auto& operator *= ( boost::numeric::ublas::tensor_core& lhs, typename boost::numeric::ublas::tensor_core::value_type r ){ - boost::numeric::ublas::detail::eval(lhs, [r](auto& l) { l*=r; } ); + using value_type = typename boost::numeric::ublas::tensor_core::value_type; + boost::numeric::ublas::detail::eval(lhs, [r](value_type& l) { l*=r; } ); return lhs; } @@ -460,7 +483,8 @@ inline constexpr auto& operator /= ( boost::numeric::ublas::tensor_core& lhs, typename boost::numeric::ublas::tensor_core::value_type r ){ - boost::numeric::ublas::detail::eval(lhs, [r](auto& l) { l/=r; } ); + using value_type = typename boost::numeric::ublas::tensor_core::value_type; + boost::numeric::ublas::detail::eval(lhs, [r](value_type& l) { l/=r; } ); return lhs; } @@ -476,8 +500,9 @@ template requires boost::numeric::ublas::detail::TensorExpression inline constexpr auto operator -(E&& e) { using tensor_type = boost::numeric::ublas::detail::real_expression_type_t; + using value_type = typename tensor_type::value_type; return boost::numeric::ublas::detail::make_unary_tensor_expression ( - std::forward(e), std::negate<>{} + std::forward(e), std::negate{} ); } diff --git a/include/boost/numeric/ublas/tensor/operators_comparison.hpp b/include/boost/numeric/ublas/tensor/operators_comparison.hpp index fa91ee29c..c2ab54c59 100644 --- a/include/boost/numeric/ublas/tensor/operators_comparison.hpp +++ b/include/boost/numeric/ublas/tensor/operators_comparison.hpp @@ -26,15 +26,44 @@ namespace boost::numeric::ublas { template class tensor_core; + } // namespace boost::numeric::ublas namespace boost::numeric::ublas::detail { +template +struct is_equality_functional_object + : std::false_type +{}; + +template +static constexpr bool is_equality_functional_object_v = is_equality_functional_object< std::decay_t >::value; + +template +struct is_equality_functional_object< std::equal_to > + : std::true_type +{}; + +template +struct is_equality_functional_object< std::not_equal_to > + : std::true_type +{}; + +template<> +struct is_equality_functional_object< std::equal_to<> > + : std::true_type +{}; + +template<> +struct is_equality_functional_object< std::not_equal_to<> > + : std::true_type +{}; + template [[nodiscard]] inline constexpr bool compare(tensor_expression const& lhs, tensor_expression const& rhs, BinaryPred&& pred) noexcept - requires ( same_exp< BinaryPred, std::equal_to<> > || same_exp< BinaryPred, std::not_equal_to<> > ) + requires is_equality_functional_object_v { auto const& lexpr = cast_tensor_expression(lhs); @@ -98,6 +127,7 @@ constexpr bool compare(tensor_expression const& lhs, tensor_expression > && is_static_v< std::decay_t< decltype(retrieve_extents(rhs)) > > ) + requires ( not is_equality_functional_object_v ) { auto const& lexpr = cast_tensor_expression(lhs); auto const& rexpr = cast_tensor_expression(rhs); @@ -203,16 +233,20 @@ template [[nodiscard]] inline constexpr bool operator==( boost::numeric::ublas::detail::tensor_expression const& lhs, - boost::numeric::ublas::detail::tensor_expression const& rhs) noexcept{ - return boost::numeric::ublas::detail::compare( lhs, rhs, std::equal_to<>{} ); + boost::numeric::ublas::detail::tensor_expression const& rhs +) noexcept{ + using value_type = typename T1::value_type; + return boost::numeric::ublas::detail::compare( lhs, rhs, std::equal_to{} ); } template [[nodiscard]] inline constexpr auto operator!=( boost::numeric::ublas::detail::tensor_expression const& lhs, - boost::numeric::ublas::detail::tensor_expression const& rhs) noexcept{ - return boost::numeric::ublas::detail::compare( lhs, rhs, std::not_equal_to<>{} ); + boost::numeric::ublas::detail::tensor_expression const& rhs +) noexcept{ + using value_type = typename T1::value_type; + return boost::numeric::ublas::detail::compare( lhs, rhs, std::not_equal_to{} ); } template @@ -224,7 +258,8 @@ constexpr auto operator< ( boost::numeric::ublas::is_static_v< std::decay_t< decltype(boost::numeric::ublas::detail::retrieve_extents(lhs)) > > && boost::numeric::ublas::is_static_v< std::decay_t< decltype(boost::numeric::ublas::detail::retrieve_extents(rhs)) > > ) { - return boost::numeric::ublas::detail::compare( lhs, rhs, std::less<>{} ); + using value_type = typename T1::value_type; + return boost::numeric::ublas::detail::compare( lhs, rhs, std::less{} ); } template @@ -236,7 +271,8 @@ constexpr auto operator<=( boost::numeric::ublas::is_static_v< std::decay_t< decltype(boost::numeric::ublas::detail::retrieve_extents(lhs)) > > && boost::numeric::ublas::is_static_v< std::decay_t< decltype(boost::numeric::ublas::detail::retrieve_extents(rhs)) > > ) { - return boost::numeric::ublas::detail::compare( lhs, rhs, std::less_equal<>{} ); + using value_type = typename T1::value_type; + return boost::numeric::ublas::detail::compare( lhs, rhs, std::less_equal{} ); } template @@ -248,7 +284,8 @@ constexpr auto operator> ( boost::numeric::ublas::is_static_v< std::decay_t< decltype(boost::numeric::ublas::detail::retrieve_extents(lhs)) > > && boost::numeric::ublas::is_static_v< std::decay_t< decltype(boost::numeric::ublas::detail::retrieve_extents(rhs)) > > ) { - return boost::numeric::ublas::detail::compare( lhs, rhs, std::greater<>{} ); + using value_type = typename T1::value_type; + return boost::numeric::ublas::detail::compare( lhs, rhs, std::greater{} ); } template @@ -260,7 +297,8 @@ constexpr auto operator>=( boost::numeric::ublas::is_static_v< std::decay_t< decltype(boost::numeric::ublas::detail::retrieve_extents(lhs)) > > && boost::numeric::ublas::is_static_v< std::decay_t< decltype(boost::numeric::ublas::detail::retrieve_extents(rhs)) > > ) { - return boost::numeric::ublas::detail::compare( lhs, rhs, std::greater_equal<>{} ); + using value_type = typename T1::value_type; + return boost::numeric::ublas::detail::compare( lhs, rhs, std::greater_equal{} ); } @@ -270,64 +308,77 @@ constexpr auto operator>=( template [[nodiscard]] inline constexpr bool operator==( typename T::value_type lhs, boost::numeric::ublas::detail::tensor_expression const& rhs) noexcept{ - return boost::numeric::ublas::detail::compare( rhs, [lhs](auto const& r){ return lhs == r; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( rhs, [lhs](value_type const& r){ return lhs == r; } ); } template [[nodiscard]] inline constexpr auto operator!=( typename T::value_type lhs, boost::numeric::ublas::detail::tensor_expression const& rhs) noexcept{ - return boost::numeric::ublas::detail::compare( rhs, [lhs](auto const& r){ return lhs != r; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( rhs, [lhs](value_type const& r){ return lhs != r; } ); } template [[nodiscard]] inline constexpr auto operator< ( typename T::value_type lhs, boost::numeric::ublas::detail::tensor_expression const& rhs) noexcept{ - return boost::numeric::ublas::detail::compare( rhs, [lhs](auto const& r){ return lhs < r; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( rhs, [lhs](value_type const& r){ return lhs < r; } ); } template [[nodiscard]] inline constexpr auto operator<=( typename T::value_type lhs, boost::numeric::ublas::detail::tensor_expression const& rhs) noexcept{ - return boost::numeric::ublas::detail::compare( rhs, [lhs](auto const& r){ return lhs <= r; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( rhs, [lhs](value_type const& r){ return lhs <= r; } ); } template [[nodiscard]] inline constexpr auto operator> ( typename T::value_type lhs, boost::numeric::ublas::detail::tensor_expression const& rhs) noexcept{ - return boost::numeric::ublas::detail::compare( rhs, [lhs](auto const& r){ return lhs > r; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( rhs, [lhs](value_type const& r){ return lhs > r; } ); } template [[nodiscard]] inline constexpr auto operator>=( typename T::value_type lhs, boost::numeric::ublas::detail::tensor_expression const& rhs) noexcept{ - return boost::numeric::ublas::detail::compare( rhs, [lhs](auto const& r){ return lhs >= r; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( rhs, [lhs](value_type const& r){ return lhs >= r; } ); } template +[[nodiscard]] inline constexpr bool operator==( boost::numeric::ublas::detail::tensor_expression const& lhs, typename T::value_type rhs) noexcept{ - return boost::numeric::ublas::detail::compare( lhs, [rhs](auto const& l){ return l == rhs; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( lhs, [rhs](value_type const& l){ return l == rhs; } ); } template [[nodiscard]] inline constexpr auto operator!=( boost::numeric::ublas::detail::tensor_expression const& lhs, typename T::value_type rhs) noexcept{ - return boost::numeric::ublas::detail::compare( lhs, [rhs](auto const& l){ return l != rhs; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( lhs, [rhs](value_type const& l){ return l != rhs; } ); } template [[nodiscard]] inline constexpr auto operator< ( boost::numeric::ublas::detail::tensor_expression const& lhs, typename T::value_type rhs) noexcept{ - return boost::numeric::ublas::detail::compare( lhs, [rhs](auto const& l){ return l < rhs; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( lhs, [rhs](value_type const& l){ return l < rhs; } ); } template [[nodiscard]] inline constexpr auto operator<=( boost::numeric::ublas::detail::tensor_expression const& lhs, typename T::value_type rhs) noexcept{ - return boost::numeric::ublas::detail::compare( lhs, [rhs](auto const& l){ return l <= rhs; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( lhs, [rhs](value_type const& l){ return l <= rhs; } ); } template [[nodiscard]] inline constexpr auto operator> ( boost::numeric::ublas::detail::tensor_expression const& lhs, typename T::value_type rhs) noexcept{ - return boost::numeric::ublas::detail::compare( lhs, [rhs](auto const& l){ return l > rhs; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( lhs, [rhs](value_type const& l){ return l > rhs; } ); } template [[nodiscard]] inline constexpr auto operator>=( boost::numeric::ublas::detail::tensor_expression const& lhs, typename T::value_type rhs) noexcept{ - return boost::numeric::ublas::detail::compare( lhs, [rhs](auto const& l){ return l >= rhs; } ); + using value_type = typename T::value_type; + return boost::numeric::ublas::detail::compare( lhs, [rhs](value_type const& l){ return l >= rhs; } ); }