diff --git a/playground/CMakeLists.txt b/playground/CMakeLists.txt index ad1f526c..cde32018 100644 --- a/playground/CMakeLists.txt +++ b/playground/CMakeLists.txt @@ -10,6 +10,7 @@ set(REAL_SRCS efunc_posits.cpp type_test.cpp float_to_decimal_string.cpp lazy_evaluation.cpp + expression_templates.cpp ) compile_all("true" "playground" "Playground" "${REAL_SRCS}") diff --git a/playground/expression_templates.cpp b/playground/expression_templates.cpp new file mode 100644 index 00000000..0b8f61aa --- /dev/null +++ b/playground/expression_templates.cpp @@ -0,0 +1,986 @@ +// lazy_evaluation.cpp: experiments in lazy evaluation and state management +// +// Copyright (C) 2017 Stillwater Supercomputing, Inc. +// SPDX-License-Identifier: MIT +// +// This file is part of the universal numbers project, which is released under an MIT Open Source license. +#include +#include +#include +#include +#include +#include + +#include + +namespace test4 { + + template + class Expression { + public: + virtual T evaluate() const = 0; + virtual ~Expression() = default; + }; + + template + class Constant : public Expression { + T value; + public: + explicit Constant(T val) : value(val) {} + T evaluate() const override { return value; } + }; + + template + class BinaryOperation : public Expression { + std::shared_ptr> left, right; + std::function op; + public: + BinaryOperation(std::shared_ptr> l, std::shared_ptr> r, std::function operation) + : left(l), right(r), op(operation) {} + T evaluate() const override { + return op(left->evaluate(), right->evaluate()); + } + }; + + template + class LazyExpression { + std::shared_ptr> expr; + public: + LazyExpression(T value) : expr(std::make_shared>(value)) {} + + LazyExpression(std::shared_ptr> e) : expr(e) {} + + LazyExpression operator+(const LazyExpression& other) const { + return LazyExpression(std::make_shared>(expr, other.expr, std::plus())); + } + + LazyExpression operator-(const LazyExpression& other) const { + return LazyExpression(std::make_shared>(expr, other.expr, std::minus())); + } + + LazyExpression operator*(const LazyExpression& other) const { + return LazyExpression(std::make_shared>(expr, other.expr, std::multiplies())); + } + + LazyExpression operator/(const LazyExpression& other) const { + return LazyExpression(std::make_shared>(expr, other.expr, [](T a, T b) { + if (b == T(0)) throw std::runtime_error("Division by zero"); + return a / b; + })); + } + + T evaluate() const { + return expr->evaluate(); + } + }; +} + +/* +namespace test5 { + +this incarnation has a problem with +1>F:\Users\tomtz\dev\clones\universal\playground\lazy_evaluation.cpp(243,60): error C2665: 'test5::Constant::Constant': no overloaded function could convert all the argument types +1>F:\Users\tomtz\dev\clones\universal\playground\lazy_evaluation.cpp(243,60): error C2665: with +1>F:\Users\tomtz\dev\clones\universal\playground\lazy_evaluation.cpp(243,60): error C2665: [ +1>F:\Users\tomtz\dev\clones\universal\playground\lazy_evaluation.cpp(243,60): error C2665: T=double +1>F:\Users\tomtz\dev\clones\universal\playground\lazy_evaluation.cpp(243,60): error C2665: ] + + // Forward declarations + template + struct Constant; + + template + struct BinaryExpression; + + // CRTP base class for all expressions + template + struct Expression { + constexpr const Derived& self() const { return static_cast(*this); } + + // This function will be implemented by derived classes + constexpr auto evaluate() const { return self().eval_impl(); } + }; + + // Constant expression + template + struct Constant : Expression> { + const T value; + explicit constexpr Constant(const T& v) : value(v) {} + constexpr T eval_impl() const { return value; } + }; + + // Binary expression + template + struct BinaryExpression : Expression> { + const Left left; + const Right right; + constexpr BinaryExpression(const Left& l, const Right& r) : left(l), right(r) {} + constexpr auto eval_impl() const { return Op::apply(left.evaluate(), right.evaluate()); } + }; + + // Operator structs + struct Add { + template + static constexpr auto apply(const T& a, const U& b) { return a + b; } + }; + + struct Subtract { + template + static constexpr auto apply(const T& a, const U& b) { return a - b; } + }; + + struct Multiply { + template + static constexpr auto apply(const T& a, const U& b) { return a * b; } + }; + + struct Divide { + template + static constexpr auto apply(const T& a, const U& b) { return a / b; } + }; + + // Helper function to create constant expressions + template + constexpr auto make_constant(const T& value) { + return Constant(value); + } + + // Operator overloads + template + constexpr auto operator+(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator-(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator*(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator/(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + // LazyExpression wrapper for easy use + template + class LazyExpression { + Expr expr; + public: + constexpr LazyExpression(const Expr& e) : expr(e) {} + + // Allow implicit conversion from any Expression to LazyExpression + template, OtherExpr>>> + constexpr LazyExpression(const OtherExpr& e) : expr(e) {} + + constexpr auto evaluate() const { return expr.evaluate(); } + + template + constexpr auto operator+(const LazyExpression& other) const { + return LazyExpression(expr + other.expr); + } + + template + constexpr auto operator-(const LazyExpression& other) const { + return LazyExpression(expr - other.expr); + } + + template + constexpr auto operator*(const LazyExpression& other) const { + return LazyExpression(expr * other.expr); + } + + template + constexpr auto operator/(const LazyExpression& other) const { + return LazyExpression(expr / other.expr); + } + }; + + // Helper function to create LazyExpressions + template + constexpr auto make_lazy(const T& value) { + return LazyExpression(make_constant(value)); + } +} +*/ + +/* +namespace test5 { + + 1. Introduced an expression_constructor_tag to disambiguate constructors. + 2. Modified the Constant class to have two distinct constructors: + One for non-Expression types (direct values) + One for Expression types (which evaluates the expression) + 3. Updated the LazyExpression class to have separate constructors for Expression and non-Expression types. + 4. Removed the make_constant function as it's no longer necessary with the new LazyExpression design. + + but this fails as the Constant constructors have the same signature and fail to compile + + // Forward declarations + template + struct Constant; + + template + struct BinaryExpression; + + // Tag type for disambiguating constructors + struct expression_constructor_tag {}; + + // CRTP base class for all expressions + template + struct Expression { + constexpr const Derived& self() const { return static_cast(*this); } + constexpr auto evaluate() const { return self().eval_impl(); } + }; + + // Constant expression + template + struct Constant : Expression> { + const T value; + + // Constructor for non-Expression types + template, U>>> + explicit constexpr Constant(const U& v) : value(v) {} + + // Constructor for Expression types + template, U>>> + explicit constexpr Constant(const U& expr, expression_constructor_tag) + : value(expr.evaluate()) {} + + constexpr T eval_impl() const { return value; } + }; + + // Binary expression + template + struct BinaryExpression : Expression> { + const Left left; + const Right right; + constexpr BinaryExpression(const Left& l, const Right& r) : left(l), right(r) {} + constexpr auto eval_impl() const { return Op::apply(left.evaluate(), right.evaluate()); } + }; + + // Operator structs (unchanged) + struct Add { + template + static constexpr auto apply(const T& a, const U& b) { return a + b; } + }; + + struct Subtract { + template + static constexpr auto apply(const T& a, const U& b) { return a - b; } + }; + + struct Multiply { + template + static constexpr auto apply(const T& a, const U& b) { return a * b; } + }; + + struct Divide { + template + static constexpr auto apply(const T& a, const U& b) { return a / b; } + }; + + // Helper function to create constant expressions + template + constexpr auto make_constant(const T& value) { + return Constant(value); + } + + // Operator overloads (unchanged) + template + constexpr auto operator+(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator-(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator*(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator/(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + // LazyExpression wrapper for easy use + template + class LazyExpression { + Expr expr; + public: + // Constructor for Expression types + template, E>>> + constexpr LazyExpression(const E& e) : expr(e) {} + + // Constructor for non-Expression types + template, T>>> + constexpr LazyExpression(const T& value) : expr(Constant(value)) {} + + constexpr auto evaluate() const { return expr.evaluate(); } + + // Operator overloads (unchanged) + template + constexpr auto operator+(const LazyExpression& other) const { + return LazyExpression(expr + other.expr); + } + + template + constexpr auto operator-(const LazyExpression& other) const { + return LazyExpression(expr - other.expr); + } + + template + constexpr auto operator*(const LazyExpression& other) const { + return LazyExpression(expr * other.expr); + } + + template + constexpr auto operator/(const LazyExpression& other) const { + return LazyExpression(expr / other.expr); + } + }; + + // Helper function to create LazyExpressions + template + constexpr auto make_lazy(const T& value) { + return LazyExpression>(value); + } +} +*/ + +/* +namespace test5 { + + 1. The Constant class now has a single, unambiguous constructor. + 2. We've introduced a type trait is_expression to differentiate between Expression and non-Expression types. + 3. The LazyExpression class now uses SFINAE to properly distinguish between Expression and non-Expression types in its constructors. + 4. We've simplified the overall design, removing unnecessary complexity. + 5. The make_lazy function has been updated to use perfect forwarding, allowing it to handle both lvalues and rvalues correctly. + + // Forward declarations + template + struct Constant; + + template + struct BinaryExpression; + + // CRTP base class for all expressions + template + struct Expression { + constexpr const Derived& self() const { return static_cast(*this); } + constexpr auto evaluate() const { return self().eval_impl(); } + }; + + // Type trait to check if a type is an Expression + template + struct is_expression : std::false_type {}; + + template + struct is_expression> : std::true_type {}; + + template + inline constexpr bool is_expression_v = is_expression::value; + + // Constant expression + template + struct Constant : Expression> { + const T value; + + // Single constructor for Constant + explicit constexpr Constant(const T& v) : value(v) {} + + constexpr T eval_impl() const { return value; } + }; + + // Binary expression + template + struct BinaryExpression : Expression> { + const Left left; + const Right right; + constexpr BinaryExpression(const Left& l, const Right& r) : left(l), right(r) {} + constexpr auto eval_impl() const { return Op::apply(left.evaluate(), right.evaluate()); } + }; + + // Operator structs + struct Add { + template + static constexpr auto apply(const T& a, const U& b) { return a + b; } + }; + + struct Subtract { + template + static constexpr auto apply(const T& a, const U& b) { return a - b; } + }; + + struct Multiply { + template + static constexpr auto apply(const T& a, const U& b) { return a * b; } + }; + + struct Divide { + template + static constexpr auto apply(const T& a, const U& b) { return a / b; } + }; + + // Operator overloads + template + constexpr auto operator+(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator-(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator*(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator/(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + // LazyExpression wrapper for easy use + template + class LazyExpression { + Expr expr; + public: + // Constructor for Expression types + template>>>> + constexpr LazyExpression(E&& e) : expr(std::forward(e)) {} + + // Constructor for non-Expression types + template>>>> + constexpr LazyExpression(T&& value) : expr(Constant>>(std::forward(value))) {} + + constexpr auto evaluate() const { return expr.evaluate(); } + + // Operator overloads + template + constexpr auto operator+(const LazyExpression& other) const { + return LazyExpression(expr + other.expr); + } + + template + constexpr auto operator-(const LazyExpression& other) const { + return LazyExpression(expr - other.expr); + } + + template + constexpr auto operator*(const LazyExpression& other) const { + return LazyExpression(expr * other.expr); + } + + template + constexpr auto operator/(const LazyExpression& other) const { + return LazyExpression(expr / other.expr); + } + }; + + // Helper function to create LazyExpressions + template + constexpr auto make_lazy(T&& value) { + return LazyExpression>>(std::forward(value)); + } + +} +*/ + +/* +namespace test5 { + + 1. The LazyExpression class now has a single, templated constructor. This avoids the ambiguity problem we were facing with multiple constructors. + 2. We've moved the logic for differentiating between Expression and non-Expression types to the make_lazy function. This function uses if constexpr to determine at compile-time whether to wrap the value in a Constant or use it directly. + 3. The Constant and BinaryExpression classes remain largely unchanged, maintaining their simplicity. + 4. We've removed the separate is_expression type trait, as it's no longer needed with this approach. + + fails on Constant<> constructor not being disambiguated between &&, const &, and const T& + + // Forward declarations + template + struct Constant; + + template + struct BinaryExpression; + + // CRTP base class for all expressions + template + struct Expression { + constexpr const Derived& self() const { return static_cast(*this); } + constexpr auto evaluate() const { return self().eval_impl(); } + }; + + // Constant expression + template + struct Constant : Expression> { + const T value; + explicit constexpr Constant(const T& v) : value(v) {} + constexpr T eval_impl() const { return value; } + }; + + // Binary expression + template + struct BinaryExpression : Expression> { + const Left left; + const Right right; + constexpr BinaryExpression(const Left& l, const Right& r) : left(l), right(r) {} + constexpr auto eval_impl() const { return Op::apply(left.evaluate(), right.evaluate()); } + }; + + // Operator structs + struct Add { + template + static constexpr auto apply(const T& a, const U& b) { return a + b; } + }; + + struct Subtract { + template + static constexpr auto apply(const T& a, const U& b) { return a - b; } + }; + + struct Multiply { + template + static constexpr auto apply(const T& a, const U& b) { return a * b; } + }; + + struct Divide { + template + static constexpr auto apply(const T& a, const U& b) { return a / b; } + }; + + // Operator overloads + template + constexpr auto operator+(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator-(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator*(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator/(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + // LazyExpression wrapper + template + class LazyExpression { + Expr expr; + + public: + // Single constructor + template + constexpr explicit LazyExpression(E&& e) : expr(std::forward(e)) {} + + constexpr auto evaluate() const { return expr.evaluate(); } + + // Operator overloads + template + constexpr auto operator+(const LazyExpression& other) const { + return LazyExpression(expr + other.expr); + } + + template + constexpr auto operator-(const LazyExpression& other) const { + return LazyExpression(expr - other.expr); + } + + template + constexpr auto operator*(const LazyExpression& other) const { + return LazyExpression(expr * other.expr); + } + + template + constexpr auto operator/(const LazyExpression& other) const { + return LazyExpression(expr / other.expr); + } + }; + + // Helper functions to create LazyExpressions + template + constexpr auto make_lazy(T&& value) { + if constexpr (std::is_base_of_v>>, + std::remove_cv_t>>) { + return LazyExpression>>(std::forward(value)); + } + else { + return LazyExpression>>>( + Constant>>(std::forward(value)) + ); + } + } +} +*/ + +/* +namespace test5 { + + 1. The Constant class now has: + A single constructor template that only accepts non-Expression types. + Deleted copy and move constructors to prevent ambiguity. + 2. The BinaryExpression now takes its arguments by value and moves them into place, which avoids the reference ambiguity. + 3. The operator structs (Add, Subtract, etc.) now take their arguments by value to match the changes in BinaryExpression. + 4. The make_lazy function remains unchanged, as it was already correctly handling the creation of Constant objects. + + These changes should resolve the ambiguity issues we were facing. + The Constant class can now only be constructed from non-Expression types, + and the BinaryExpression avoids reference ambiguity by taking its arguments by value. + + // Forward declarations + template + struct Constant; + + template + struct BinaryExpression; + + // CRTP base class for all expressions + template + struct Expression { + constexpr const Derived& self() const { return static_cast(*this); } + constexpr auto evaluate() const { return self().eval_impl(); } + }; + + // Constant expression + template + struct Constant : Expression> { + T value; + + // Constructor for non-Expression types + template>>, std::remove_cv_t>>>> + explicit constexpr Constant(U&& v) : value(std::forward(v)) {} + + // Deleted copy and move constructors to avoid ambiguity + Constant(const Constant&) = delete; + Constant(Constant&&) = delete; + + constexpr T eval_impl() const { return value; } + }; + + // Binary expression + template + struct BinaryExpression : Expression> { + Left left; + Right right; + + constexpr BinaryExpression(Left l, Right r) : left(std::move(l)), right(std::move(r)) {} + + constexpr auto eval_impl() const { return Op::apply(left.evaluate(), right.evaluate()); } + }; + + // Operator structs + struct Add { + template + static constexpr auto apply(T a, U b) { return a + b; } + }; + + struct Subtract { + template + static constexpr auto apply(T a, U b) { return a - b; } + }; + + struct Multiply { + template + static constexpr auto apply(T a, U b) { return a * b; } + }; + + struct Divide { + template + static constexpr auto apply(T a, U b) { return a / b; } + }; + + // Operator overloads + template + constexpr auto operator+(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator-(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator*(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator/(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + // LazyExpression wrapper + template + class LazyExpression { + Expr expr; + + public: + template + constexpr explicit LazyExpression(E&& e) : expr(std::forward(e)) {} + + constexpr auto evaluate() const { return expr.evaluate(); } + + // Operator overloads + template + constexpr auto operator+(const LazyExpression& other) const { + return LazyExpression(expr + other.expr); + } + + template + constexpr auto operator-(const LazyExpression& other) const { + return LazyExpression(expr - other.expr); + } + + template + constexpr auto operator*(const LazyExpression& other) const { + return LazyExpression(expr * other.expr); + } + + template + constexpr auto operator/(const LazyExpression& other) const { + return LazyExpression(expr / other.expr); + } + }; + + // Helper function to create LazyExpressions + template + constexpr auto make_lazy(T&& value) { + if constexpr (std::is_base_of_v>>, + std::remove_cv_t>>) { + return LazyExpression>>(std::forward(value)); + } + else { + return LazyExpression>>>( + Constant>>(std::forward(value)) + ); + } + } + +} +*/ + +#undef ET_EXPERIMENT +#ifdef ET_EXPERIMENT +namespace test5 { + + /* + 1. Reintroduced copy constructors for Constant and BinaryExpression. + 2. Added move constructors for Constant and BinaryExpression to support efficient moves when possible. + 3. Modified BinaryExpression to take its arguments by const reference, allowing for proper copying. + 4. Added copy and move constructors to LazyExpression to ensure it can be properly copied or moved. + 5. Reverted the operator structs to take their arguments by const reference, matching the BinaryExpression changes. + + These changes allow for proper copying of expressions while maintaining the benefits of lazy evaluation and avoiding ambiguity. + The LazyExpression operators can now correctly create BinaryExpression instances without running into deleted copy constructor issues. + + */ + + // Forward declarations + template + struct Constant; + + template + struct BinaryExpression; + + // CRTP base class for all expressions + template + struct Expression { + constexpr const Derived& self() const { return static_cast(*this); } + constexpr auto evaluate() const { return self().eval_impl(); } + }; + + // Constant expression + template + struct Constant : Expression> { + T value; + + // Constructor for non-Expression types + template>>, std::remove_cv_t>>>> + explicit constexpr Constant(U&& v) : value(std::forward(v)) {} + + // Copy constructor + constexpr Constant(const Constant& other) : value(other.value) {} + + // Move constructor + constexpr Constant(Constant&& other) noexcept : value(std::move(other.value)) {} + + constexpr T eval_impl() const { return value; } + }; + + // Binary expression + template + struct BinaryExpression : Expression> { + Left left; + Right right; + + constexpr BinaryExpression(const Left& l, const Right& r) : left(l), right(r) {} + + // Copy constructor + constexpr BinaryExpression(const BinaryExpression& other) : left(other.left), right(other.right) {} + + // Move constructor + constexpr BinaryExpression(BinaryExpression&& other) noexcept + : left(std::move(other.left)), right(std::move(other.right)) {} + + constexpr auto eval_impl() const { return Op::apply(left.evaluate(), right.evaluate()); } + }; + + // Operator structs + struct Add { + template + static constexpr auto apply(const T& a, const U& b) { return a + b; } + }; + + struct Subtract { + template + static constexpr auto apply(const T& a, const U& b) { return a - b; } + }; + + struct Multiply { + template + static constexpr auto apply(const T& a, const U& b) { return a * b; } + }; + + struct Divide { + template + static constexpr auto apply(const T& a, const U& b) { return a / b; } + }; + + // Operator overloads + template + constexpr auto operator+(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator-(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator*(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + template + constexpr auto operator/(const Expression& lhs, const Expression& rhs) { + return BinaryExpression(lhs.self(), rhs.self()); + } + + // LazyExpression wrapper + template + class LazyExpression { + Expr expr; + + public: + template + constexpr explicit LazyExpression(E&& e) : expr(std::forward(e)) {} + + // Copy constructor + constexpr LazyExpression(const LazyExpression& other) : expr(other.expr) {} + + // Move constructor + constexpr LazyExpression(LazyExpression&& other) noexcept : expr(std::move(other.expr)) {} + + constexpr auto evaluate() const { return expr.evaluate(); } + + // Operator overloads + template + constexpr auto operator+(const LazyExpression& other) const { + return LazyExpression(expr + other.expr); + } + + template + constexpr auto operator-(const LazyExpression& other) const { + return LazyExpression(expr - other.expr); + } + + template + constexpr auto operator*(const LazyExpression& other) const { + return LazyExpression(expr * other.expr); + } + + template + constexpr auto operator/(const LazyExpression& other) const { + return LazyExpression(expr / other.expr); + } + }; + + // Helper function to create LazyExpressions + template + constexpr auto make_lazy(T&& value) { + if constexpr (std::is_base_of_v>>, + std::remove_cv_t>>) { + return LazyExpression>>(std::forward(value)); + } + else { + return LazyExpression>>>( + Constant>>(std::forward(value)) + ); + } + } +} +#endif + +int main(int argc, char** argv) +try { + using namespace sw::universal; + + { + std::cout << "Test4: polymorphic lazy evaluation\n"; + test4::LazyExpression a(5.0); + test4::LazyExpression b(3.0); + auto result = (a + b) * (a - b); + std::cout << result.evaluate(); + } + +#ifdef ET_EXPERIMENT + { + std::cout << "Test5: expression template lazy evaluation\n"; + auto a = test5::make_lazy(5.0); + auto b = test5::make_lazy(3.0); + auto result = (a + b) * (a - b); + std::cout << result.evaluate(); + } +#endif + + return EXIT_SUCCESS; +} +catch (char const* msg) { + std::cerr << "Caught exception: " << msg << std::endl; + return EXIT_FAILURE; +} +catch (const sw::universal::universal_arithmetic_exception& err) { + std::cerr << "Uncaught universal arithmetic exception: " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (const sw::universal::universal_internal_exception& err) { + std::cerr << "Uncaught universal internal exception: " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (const std::runtime_error& err) { + std::cerr << "Uncaught runtime exception: " << err.what() << std::endl; + return EXIT_FAILURE; +} +catch (...) { + std::cerr << "Caught unknown exception" << std::endl; + return EXIT_FAILURE; +} diff --git a/playground/lazy_evaluation.cpp b/playground/lazy_evaluation.cpp index 1499861e..bd3c820e 100644 --- a/playground/lazy_evaluation.cpp +++ b/playground/lazy_evaluation.cpp @@ -6,6 +6,10 @@ // This file is part of the universal numbers project, which is released under an MIT Open Source license. #include #include +#include +#include +#include +#include #include @@ -83,6 +87,70 @@ namespace test3 { }; } +namespace test4 { + + // polymorphic lazy evaluation + + template + class Expression { + public: + virtual T evaluate() const = 0; + virtual ~Expression() = default; + }; + + template + class Constant : public Expression { + T value; + public: + explicit Constant(T val) : value(val) {} + T evaluate() const override { return value; } + }; + + template + class BinaryOperation : public Expression { + std::shared_ptr> left, right; + std::function op; + public: + BinaryOperation(std::shared_ptr> l, std::shared_ptr> r, std::function operation) + : left(l), right(r), op(operation) {} + T evaluate() const override { + return op(left->evaluate(), right->evaluate()); + } + }; + + template + class LazyExpression { + std::shared_ptr> expr; + public: + LazyExpression(T value) : expr(std::make_shared>(value)) {} + + LazyExpression(std::shared_ptr> e) : expr(e) {} + + LazyExpression operator+(const LazyExpression& other) const { + return LazyExpression(std::make_shared>(expr, other.expr, std::plus())); + } + + LazyExpression operator-(const LazyExpression& other) const { + return LazyExpression(std::make_shared>(expr, other.expr, std::minus())); + } + + LazyExpression operator*(const LazyExpression& other) const { + return LazyExpression(std::make_shared>(expr, other.expr, std::multiplies())); + } + + LazyExpression operator/(const LazyExpression& other) const { + return LazyExpression(std::make_shared>(expr, other.expr, [](T a, T b) { + if (b == T(0)) throw std::runtime_error("Division by zero"); + return a / b; + })); + } + + T evaluate() const { + return expr->evaluate(); + } + }; +} + int main(int argc, char** argv) try { using namespace sw::universal; @@ -91,30 +159,37 @@ try { // precondition is definition of operator+(), operator*(), and conversion operator { - using Real = float; // cfloat<24, 8, uint32_t, true, false, false>; + std::cout << "Test1: expression template lazy evaluation\n"; + using Real = cfloat<24, 8, uint32_t, true, false, false>; Real ra(2.0), rb(3.0), rc(4.0); - decltype(ra * rb) mulType = (ra * rb); // mulType is a Real - std::cout << "should be a cfloat : " << typeid(mulType).name() << '\n'; - decltype(rb + rc) addType = (rb + rc); // addType is a Real - std::cout << "should be a cfloat : " << typeid(addType).name() << '\n'; - + { + decltype(ra * rb) mulType = (ra * rb); // mulType is a Real + std::cout << "should be a cfloat : " << typeid(mulType).name() << '\n'; + decltype(rb + rc) addType = (rb + rc); // addType is a Real + std::cout << "should be a cfloat : " << typeid(addType).name() << '\n'; + } test1::Expression a(2.0), b(3.0), c(4.0); std::cout << "should be an Expression : " << typeid(a).name() << '\n'; - std::cout << "should be an Expression : " << typeid(a + b).name() << '\n'; - std::cout << "should be an Expression : " << typeid(a * b).name() << '\n'; - - //auto mulType = a * b; // mulType should become an Expression - //std::cout << typeid(mulType).name() << '\n'; - //decltype(b + c) addType = (b + c); // addType should become an Expression - //std::cout << typeid(addType).name() << '\n'; + // these statements fail during argument deduction + // std::cout << "should be an Expression : " << typeid(decltype(a + b)).name() << '\n'; + // std::cout << "should be an Expression : " << typeid(decltype(a * b)).name() << '\n'; + /* + { + test1::Expression mulType = a * b; // mulType should become an Expression + std::cout << typeid(mulType).name() << '\n'; + decltype(b + c) addType = (b + c); // addType should become an Expression + std::cout << typeid(addType).name() << '\n'; + } + */ } { + std::cout << "Test2: expression template lazy evaluation\n"; // using Real = cfloat<24,8, uint32_t, true, false, false>; using Real = float; test1::Expression a(2.0), b(3.0), c(4.0); @@ -130,12 +205,13 @@ try { } { + std::cout << "Test3: expression template lazy evaluation\n"; // using Real = cfloat<24,8, uint32_t, true, false, false>; using Real = float; - test2::Expression a(2.5), b(3.0), c(4.0); + test2::Expression a(2.0), b(3.0), c(4.0); - //test2::Expression result = a * (b + c); + //test3::Expression result = a * (b + c); // required: binary operator that supports: Expression * cfloat decltype(b + c) bla = (b + c); // bla is a Real @@ -150,6 +226,23 @@ try { std::cout << value << '\n'; } + { + std::cout << "Test4a: polymorphic lazy evaluation with native types\n"; + using Real = double; + test4::LazyExpression a(2.0), b(3.0), c(4.0); + auto result = a * (b + c); + std::cout << result.evaluate() << '\n'; + + } + + { + std::cout << "Test4b: polymorphic lazy evaluation with custom types\n"; + using Real = cfloat<24, 8, uint32_t, true, false, false>; + test4::LazyExpression a(2.0), b(3.0), c(4.0); + auto result = a * (b + c); + std::cout << result.evaluate() << '\n'; + } + return EXIT_SUCCESS; } catch (char const* msg) {