diff --git a/c++/nda/clef/expression.hpp b/c++/nda/clef/expression.hpp index 5288e7d0..e906b530 100644 --- a/c++/nda/clef/expression.hpp +++ b/c++/nda/clef/expression.hpp @@ -114,10 +114,27 @@ namespace nda::clef { * @return An nda::clef::expr object with the nda::clef::tags::function tag containing the current expression node * as the first child node and the other arguments as the remaining child nodes. */ +#ifdef __cpp_explicit_this_parameter + template + auto operator()(this Self &&self, Args &&...args) { + return expr...>{tags::function(), std::forward(self), std::forward(args)...}; + } +#else + template + auto operator()(Args &&...args) const & { + return expr...>{tags::function(), *this, std::forward(args)...}; + } + template - auto operator()(Args &&...args) const { + auto operator()(Args &&...args) & { return expr...>{tags::function(), *this, std::forward(args)...}; } + + template + auto operator()(Args &&...args) && { + return expr...>{tags::function(), std::move(*this), std::forward(args)...}; + } +#endif }; namespace detail { diff --git a/c++/nda/clef/operation.hpp b/c++/nda/clef/operation.hpp index 44b0e751..f435831f 100644 --- a/c++/nda/clef/operation.hpp +++ b/c++/nda/clef/operation.hpp @@ -20,6 +20,22 @@ namespace nda::clef { + /// During a partial evaluation of an expression, the + // function nodes can be evaluated in 2 ways : + // - default : the function is evaluated iif all the arguments are non lazy + // otherwise the node is kept, with its children replaced by evaluation + // i.e. the function is NOT called. + // - if true : the function is CALLED with all the arguments, lazy of not. + // It is not the default, as the function/object must be properly + // implemented, by MOVING the argument in a new function node + // with make_expr_call + template + constexpr bool supports_partial_eval_of_calls = false; + + /// Same as supports_partial_eval_of_calls but for the subscript operator + template + constexpr bool supports_partial_eval_of_subscript = false; + /** * @addtogroup clef_expr * @{ @@ -81,7 +97,9 @@ namespace nda::clef { * @return Result of the function call. */ template - FORCEINLINE decltype(auto) operator()(F &&f, Args &&...args) const { + FORCEINLINE auto operator()(F &&f, Args &&...args) + -> decltype(detail::fget(std::forward(f))(detail::fget(std::forward(args))...)) const { + // trailing decltype is necessary for requires later in operation return detail::fget(std::forward(f))(detail::fget(std::forward(args))...); } }; @@ -99,7 +117,8 @@ namespace nda::clef { * @return Result of the subscript operation. */ template - FORCEINLINE decltype(auto) operator()(F &&f, Args &&...args) const { + FORCEINLINE auto operator()(F &&f, Args &&...args) + -> decltype(detail::fget(std::forward(f)).operator[](detail::fget(std::forward(args))...)) const { // directly calling [args...] breaks clang return detail::fget(std::forward(f)).operator[](detail::fget(std::forward(args))...); } @@ -224,9 +243,18 @@ namespace nda::clef { * @param args Operands. * @return An nda::clef::expr for the given operation and operands. */ + template FORCEINLINE auto op_dispatch(std::true_type, Args &&...args) { - return expr...>{Tag(), std::forward(args)...}; + using Arg0 = std::decay_t>>; + if constexpr (not(std::is_same_v and not supports_partial_eval_of_calls) and // + not(std::is_same_v and not supports_partial_eval_of_subscript) //and // + //requires { operation()(std::forward(args)...); } + ) { + return operation()(std::forward(args)...); + } else { + return expr...>{Tag(), std::forward(args)...}; + } } /** diff --git a/test/c++/nda_clef.cpp b/test/c++/nda_clef.cpp index 7e9b61e3..722eda8f 100644 --- a/test/c++/nda_clef.cpp +++ b/test/c++/nda_clef.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -474,3 +475,52 @@ TEST_F(CLEF, SumExpressionOverDomain) { EXPECT_EQ(dom1.size(), 3); EXPECT_EQ(clef::sum(ex3, x0_ = std::vector{1, 2, 3}, x1_ = std::vector{4, 5, 6}), 5 + 2 * 6 + 3 * 7 + 2 * 8 + 9); } + +// ========= Deep Evaluation of Function Calls ========== +int f1(int x) { + //std::cerr << "Eval f1 " << x << std::endl; + return 100 * x; +} +CLEF_MAKE_FNT_LAZY(f1); + +struct _f3 { + auto operator()(auto x, auto y) const { + if constexpr (nda::clef::is_lazy) { + if constexpr (nda::clef::is_lazy) { + return make_expr_call(*this, x, y); + } else { + // std::cerr << "Eval f3 ONE LAZY " << x << " " << y << std::endl; + return f1(x) + auto{y}; + } + } else { + // std::cerr << "Eval f3 " << x << " " << y << std::endl; + return 10 * x + y; + } + } +}; +template <> +constexpr bool nda::clef::supports_partial_eval_of_calls<_f3> = true; + +static constexpr _f3 f3{}; +std::ostream &operator<<(std::ostream &out, _f3) { return out << "f3"; } + +TEST_F(CLEF, DeepEval1) { + nda::clef::placeholder<0> x_; + nda::clef::placeholder<1> y_; + nda::clef::placeholder<2> z_; + auto ex = x_ + (10 * y_ + 100 * z_); + auto ev = eval(ex, y_ = 20, z_ = 30); + std::stringstream s1, s2; + s1 << ev; + s2 << x_ + 3200; + EXPECT_EQ(s1.str(), s2.str()); + EXPECT_EQ(eval(ev, x_ = 1), 1 + 10 * 20 + 100 * 30); +} + +TEST_F(CLEF, DeepEvalFntCall) { + nda::clef::placeholder<0> x_; + nda::clef::placeholder<1> y_; + auto ex = x_ + f3(x_, y_); + auto ev = eval(ex, y_ = 20); + EXPECT_EQ(eval(ev, x_ = 1), 1 + f1(1) + 20); +}