// Copyright (c) 2018-2025 Jean-Louis Leroy // Distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) #include #include #include #include #include #include #include #include #include #include "test_util.hpp" #define BOOST_TEST_MODULE openmethod #include using namespace boost::openmethod; namespace animals { struct Animal { explicit Animal(std::string str) { name = std::move(str); } Animal(const Animal&) = delete; Animal(Animal&&) = default; virtual ~Animal() { } std::string name; }; struct Property { std::string owner = "Bill"; }; struct Dog : Property, Animal { using Animal::Animal; }; struct Cat : Property, virtual Animal { using Animal::Animal; }; } // namespace animals // ----------------------------------------------------------------------------- // pass virtual args by lvalue references namespace TEST_NS { using policy = test_policy_<__COUNTER__>; using namespace animals; BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); BOOST_OPENMETHOD(name, (virtual_), std::string, policy); BOOST_OPENMETHOD_OVERRIDE(name, (const Cat& cat), std::string) { return cat.owner + "'s cat " + cat.name; } BOOST_OPENMETHOD_OVERRIDE(name, (const Dog& dog), std::string) { return dog.owner + "'s dog " + dog.name; } BOOST_AUTO_TEST_CASE(cast_args_lvalue_refs) { initialize(); Dog spot("Spot"); BOOST_TEST(name(spot) == "Bill's dog Spot"); Cat felix("Felix"); BOOST_TEST(name(felix) == "Bill's cat Felix"); } } // namespace TEST_NS namespace TEST_NS { using policy = test_policy_<__COUNTER__>; using namespace animals; } // namespace TEST_NS // ----------------------------------------------------------------------------- // pass virtual args by rvalue references namespace TEST_NS { using policy = test_policy_<__COUNTER__>; using namespace animals; BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); BOOST_OPENMETHOD( teleport, (virtual_), std::unique_ptr, policy); BOOST_OPENMETHOD_OVERRIDE(teleport, (Cat && cat), std::unique_ptr) { return std::make_unique(std::move(cat)); } BOOST_OPENMETHOD_OVERRIDE(teleport, (Dog && dog), std::unique_ptr) { return std::make_unique(std::move(dog)); } BOOST_AUTO_TEST_CASE(cast_args_rvalue_refs) { initialize(); { Dog spot("Spot"); auto animal = teleport(std::move(spot)); BOOST_TEST(animal->name == "Spot"); BOOST_TEST(spot.name == ""); } { Cat felix("Felix"); auto animal = teleport(std::move(felix)); BOOST_TEST(animal->name == "Felix"); BOOST_TEST(felix.name == ""); } } } // namespace TEST_NS namespace TEST_NS { // ----------------------------------------------------------------------------- // pass virtual args by shared_ptr by value using policy = test_policy_<__COUNTER__>; using namespace animals; BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); BOOST_OPENMETHOD( name, (virtual_>), std::string, policy); BOOST_OPENMETHOD_OVERRIDE(name, (std::shared_ptr cat), std::string) { return cat->owner + "'s cat " + cat->name; } BOOST_OPENMETHOD_OVERRIDE(name, (std::shared_ptr dog), std::string) { return dog->owner + "'s dog " + dog->name; } BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_value) { initialize(); auto spot = std::make_shared("Spot"); BOOST_TEST(name(spot) == "Bill's dog Spot"); auto felix = std::make_shared("Felix"); BOOST_TEST(name(felix) == "Bill's cat Felix"); } } // namespace TEST_NS namespace TEST_NS { using policy = test_policy_<__COUNTER__>; using namespace animals; } // namespace TEST_NS namespace TEST_NS { // ----------------------------------------------------------------------------- // pass virtual args by shared_ptr by ref using policy = test_policy_<__COUNTER__>; using namespace animals; BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); BOOST_OPENMETHOD( name, (virtual_&>), std::string, policy); BOOST_OPENMETHOD_OVERRIDE( name, (const std::shared_ptr& cat), std::string) { return cat->owner + "'s cat " + cat->name; } BOOST_OPENMETHOD_OVERRIDE( name, (const std::shared_ptr& dog), std::string) { return dog->owner + "'s dog " + dog->name; } BOOST_AUTO_TEST_CASE(cast_args_shared_ptr_by_ref) { initialize(); auto spot = std::make_shared("Spot"); BOOST_TEST(name(spot) == "Bill's dog Spot"); auto felix = std::make_shared("Felix"); BOOST_TEST(name(felix) == "Bill's cat Felix"); } } // namespace TEST_NS namespace TEST_NS { // ----------------------------------------------------------------------------- // pass virtual args by unique_ptr using policy = test_policy_<__COUNTER__>; using namespace animals; BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); BOOST_OPENMETHOD( name, (virtual_>), std::string, policy); BOOST_OPENMETHOD_OVERRIDE(name, (std::unique_ptr cat), std::string) { return cat->owner + "'s cat " + cat->name; } BOOST_OPENMETHOD_OVERRIDE(name, (std::unique_ptr dog), std::string) { return dog->owner + "'s dog " + dog->name; } BOOST_AUTO_TEST_CASE(cast_args_unique_ptr) { initialize(); auto spot = std::make_unique("Spot"); BOOST_TEST(name(std::move(spot)) == "Bill's dog Spot"); BOOST_TEST(spot.get() == nullptr); auto felix = std::make_unique("Felix"); BOOST_TEST(name(std::move(felix)) == "Bill's cat Felix"); BOOST_TEST(felix.get() == nullptr); } } // namespace TEST_NS namespace matrices { using policy = test_policy_<__COUNTER__>; #define TYPES \ NONE, MATRIX, DIAGONAL, SCALAR_MATRIX, SCALAR_DIAGONAL, MATRIX_SCALAR, \ DIAGONAL_SCALAR, MATRIX_MATRIX, MATRIX_DIAGONAL, DIAGONAL_DIAGONAL, \ DIAGONAL_MATRIX, MATRIX_DENSE, DENSE_MATRIX enum Type { TYPES }; using Types = std::pair; std::ostream& operator<<(std::ostream& os, Type t) { #define TYPE_TO_STRING(r, data, elem) BOOST_PP_STRINGIZE(elem), static const char* names[] = {BOOST_PP_SEQ_FOR_EACH( TYPE_TO_STRING, _, BOOST_PP_VARIADIC_TO_SEQ(TYPES))}; return os << names[t]; } std::ostream& operator<<(std::ostream& os, Types o) { os << o.first << ", " << o.second; return os; } struct matrix { virtual ~matrix() { } Type type; }; struct dense_matrix : matrix {}; struct diagonal_matrix : matrix {}; } // namespace matrices namespace TEST_NS { using namespace matrices; BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, policy); BOOST_OPENMETHOD( times, (virtual_, virtual_), Types, policy); BOOST_OPENMETHOD(times, (double, virtual_), Types, policy); BOOST_OPENMETHOD(times, (virtual_, double), Types, policy); BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, const matrix&), Types) { return Types(MATRIX_MATRIX, NONE); } BOOST_OPENMETHOD_OVERRIDE( times, (const diagonal_matrix&, const diagonal_matrix&), Types) { return Types(DIAGONAL_DIAGONAL, MATRIX_MATRIX); } BOOST_OPENMETHOD_OVERRIDE(times, (double a, const matrix& m), Types) { return Types(SCALAR_MATRIX, NONE); } BOOST_OPENMETHOD_OVERRIDE(times, (double a, const diagonal_matrix& m), Types) { return Types(SCALAR_DIAGONAL, SCALAR_MATRIX); } BOOST_OPENMETHOD_OVERRIDE(times, (const diagonal_matrix& m, double a), Types) { return Types(DIAGONAL_SCALAR, MATRIX_SCALAR); } BOOST_OPENMETHOD_OVERRIDE(times, (const matrix& m, double a), Types) { return Types(MATRIX_SCALAR, NONE); } BOOST_AUTO_TEST_CASE(simple) { auto report = initialize(); { // pass by const ref const matrix& dense = dense_matrix(); const matrix& diag = diagonal_matrix(); BOOST_TEST(times(dense, dense) == Types(MATRIX_MATRIX, NONE)); BOOST_TEST( times(diag, diag) == Types(DIAGONAL_DIAGONAL, MATRIX_MATRIX)); BOOST_TEST(times(diag, dense) == Types(MATRIX_MATRIX, NONE)); BOOST_TEST(times(2, dense) == Types(SCALAR_MATRIX, NONE)); BOOST_TEST(times(dense, 2) == Types(MATRIX_SCALAR, NONE)); BOOST_TEST(times(diag, 2) == Types(DIAGONAL_SCALAR, MATRIX_SCALAR)); } } } // namespace TEST_NS namespace TEST_NS { using namespace matrices; using policy = test_policy_<__COUNTER__>; BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, policy); BOOST_OPENMETHOD( times, (virtual_, virtual_), Types, policy); BOOST_OPENMETHOD_OVERRIDE(times, (const matrix&, const matrix&), Types) { BOOST_TEST(!has_next()); return Types(MATRIX_MATRIX, NONE); } BOOST_OPENMETHOD_OVERRIDE( times, (const matrix& a, const diagonal_matrix& b), Types) { BOOST_TEST(has_next()); return Types(MATRIX_DIAGONAL, next(a, b).first); } BOOST_OPENMETHOD_OVERRIDE( times, (const diagonal_matrix& a, const matrix& b), Types) { BOOST_TEST(has_next()); return Types(DIAGONAL_MATRIX, next(a, b).first); } BOOST_AUTO_TEST_CASE(ambiguity) { auto compiler = initialize(); BOOST_TEST(compiler.report.ambiguous == 1); // N2216: in case of ambiguity, pick one. diagonal_matrix diag1, diag2; auto result1 = times(diag1, diag2); BOOST_TEST(result1.first == DIAGONAL_MATRIX); // Which overrider is picked is NOT documented! However, I know that it is // the last in registration order. This is important for the test for // ambiguity resolution using covariant return types. BOOST_TEST(result1.second == MATRIX_MATRIX); // but always the same auto result2 = times(diag1, diag2); BOOST_TEST((result1 == result2)); } } // namespace TEST_NS namespace TEST_NS { using namespace matrices; using policy = test_policy_<__COUNTER__>; BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, policy); BOOST_OPENMETHOD( times, (virtual_, virtual_), matrix*, policy); BOOST_OPENMETHOD_OVERRIDE( times, (const matrix&, const dense_matrix&), matrix*) { auto result = new dense_matrix; result->type = MATRIX_DENSE; return result; } BOOST_OPENMETHOD_OVERRIDE( times, (const dense_matrix&, const matrix&), dense_matrix*) { auto result = new dense_matrix; result->type = DENSE_MATRIX; return result; } BOOST_AUTO_TEST_CASE(covariant_return_type) { auto compiler = initialize(); BOOST_TEST(compiler.report.ambiguous == 0); // N2216: use covariant return types to resolve ambiguity. dense_matrix left, right; std::unique_ptr result(times(left, right)); BOOST_TEST(result->type == DENSE_MATRIX); } } // namespace TEST_NS namespace test_next_fn { struct Animal { virtual ~Animal() { } }; struct Dog : Animal {}; struct Bulldog : Dog {}; BOOST_OPENMETHOD_CLASSES(Animal, Dog, Bulldog); struct BOOST_OPENMETHOD_NAME(poke); using poke = method), std::string>; std::string poke_dog(Dog& dog) { return "bark"; } BOOST_OPENMETHOD_REGISTER(poke::override); std::string poke_bulldog(Bulldog& dog) { return poke::next(dog) + " and bite back"; } BOOST_OPENMETHOD_REGISTER(poke::override); BOOST_AUTO_TEST_CASE(test_next_fn) { initialize(); std::unique_ptr snoopy = std::make_unique(); BOOST_TEST(poke::fn(*snoopy) == "bark"); std::unique_ptr hector = std::make_unique(); BOOST_TEST(poke::fn(*hector) == "bark and bite back"); } } // namespace test_next_fn namespace errors { using namespace matrices; BOOST_OPENMETHOD_CLASSES(matrix, dense_matrix, diagonal_matrix, matrix); BOOST_OPENMETHOD( times, (virtual_, virtual_), void); BOOST_OPENMETHOD_OVERRIDE( times, (const diagonal_matrix&, const matrix&), void) { } BOOST_OPENMETHOD_OVERRIDE( times, (const matrix&, const diagonal_matrix&), void) { } void test_handler(const default_policy::error_variant& error_v) { if (auto error = std::get_if(&error_v)) { throw *error; } if (auto error = std::get_if(&error_v)) { throw *error; } if (auto error = std::get_if(&error_v)) { throw *error; } throw int(); } } // namespace errors namespace initialize_error_handling { using policy = test_policy_<__COUNTER__>; struct base { virtual ~base() { } }; BOOST_OPENMETHOD(foo, (virtual_), void, policy); // instantiate the method BOOST_OPENMETHOD_OVERRIDE(foo, (base&), void) { } BOOST_AUTO_TEST_CASE(test_initialize_error_handling) { auto prev_handler = policy::set_error_handler(errors::test_handler); try { initialize(); } catch (const unknown_class_error& error) { policy::set_error_handler(prev_handler); BOOST_TEST(error.type == reinterpret_cast(&typeid(base))); return; } catch (...) { policy::set_error_handler(prev_handler); BOOST_FAIL("unexpected exception"); } BOOST_FAIL("did not throw"); } } // namespace initialize_error_handling namespace across_namespaces { namespace animals { class Animal { public: virtual ~Animal() { } }; BOOST_OPENMETHOD(poke, (virtual_), std::string); } // namespace animals namespace more_animals { class Dog : public animals::Animal {}; BOOST_OPENMETHOD_CLASSES(Dog, animals::Animal); BOOST_OPENMETHOD_OVERRIDE(poke, (const Dog& dog), std::string) { return "bark"; } } // namespace more_animals BOOST_AUTO_TEST_CASE(across_namespaces) { const animals::Animal& animal = more_animals::Dog(); BOOST_TEST("bark" == poke(animal)); } } // namespace across_namespaces namespace report { using policy = test_policy_<__COUNTER__>; struct Animal { virtual void foo() = 0; }; struct Dog : Animal { void foo() override { } }; struct Cat : Animal { void foo() override { } }; struct poke_; struct pet_; struct meet_; template void fn(Class&...) { } BOOST_OPENMETHOD_CLASSES(Animal, Dog, Cat, policy); BOOST_AUTO_TEST_CASE(initialize_report) { using poke = method), void, policy>; using pet = method), void, policy>; using meet = method, virtual_), void, policy>; auto report = initialize().report; BOOST_TEST(report.not_implemented == 3); BOOST_TEST(report.ambiguous == 0); // 'meet' dispatch table is one cell, containing 'not_implemented' BOOST_TEST(report.cells == 1); BOOST_OPENMETHOD_REGISTER(poke::override>); report = initialize().report; BOOST_TEST(report.not_implemented == 2); BOOST_OPENMETHOD_REGISTER(pet::override>); BOOST_OPENMETHOD_REGISTER(pet::override>); report = initialize().report; BOOST_TEST(report.not_implemented == 2); // create ambiguity BOOST_OPENMETHOD_REGISTER(meet::override>); BOOST_OPENMETHOD_REGISTER(meet::override>); report = initialize().report; BOOST_TEST(report.cells == 4); BOOST_TEST(report.ambiguous == 1); BOOST_OPENMETHOD_REGISTER(meet::override>); report = initialize().report; BOOST_TEST(report.cells == 6); BOOST_TEST(report.ambiguous == 1); // shadow ambiguity BOOST_OPENMETHOD_REGISTER(meet::override>); BOOST_OPENMETHOD_REGISTER(meet::override>); BOOST_OPENMETHOD_REGISTER(meet::override>); report = initialize().report; BOOST_TEST(report.cells == 9); BOOST_TEST(report.ambiguous == 0); } } // namespace report namespace test_comma_in_return_type { using policy = test_policy_<__COUNTER__>; struct Test { virtual ~Test() {}; }; BOOST_OPENMETHOD_CLASSES(Test, policy); BOOST_OPENMETHOD(foo, (virtual_), std::pair, policy); BOOST_OPENMETHOD_OVERRIDE(foo, (Test&), std::pair) { return {1, 2}; } BOOST_AUTO_TEST_CASE(comma_in_return_type) { initialize(); Test test; BOOST_TEST((foo(test) == std::pair(1, 2))); } } // namespace test_comma_in_return_type