From 42e244f4d9fcd2817a6b8b6503890cac730fdeda Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Wed, 15 May 2024 18:01:26 +0200 Subject: [PATCH 1/2] Add testing for nonintrusive interface implementations. --- test/CMakeLists.txt | 1 + test/test_nonintrusive.cpp | 89 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 test/test_nonintrusive.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bd33b94..be62a61 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -52,6 +52,7 @@ ADD_TANUKI_TESTCASE(test_make_invalid) ADD_TANUKI_TESTCASE(test_emplace) ADD_TANUKI_TESTCASE(test_invalid_composite) ADD_TANUKI_TESTCASE(test_std_function) +ADD_TANUKI_TESTCASE(test_nonintrusive) ADD_TANUKI_TESTCASE(io_iterator) ADD_TANUKI_TESTCASE(input_iterator) diff --git a/test/test_nonintrusive.cpp b/test/test_nonintrusive.cpp new file mode 100644 index 0000000..665068d --- /dev/null +++ b/test/test_nonintrusive.cpp @@ -0,0 +1,89 @@ +#include + +#include + +#include + +// NOLINTBEGIN(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) + +namespace ns +{ + +struct my_iface { + virtual ~my_iface() = default; + virtual int foo() const = 0; +}; + +// Also include an interface with an impl template +// to check the non-intrusive representation takes +// the precedence. +struct my_iface2 { + virtual ~my_iface2() = default; + virtual int bar() const = 0; + + template + struct impl { + }; +}; + +} // namespace ns + +namespace tanuki +{ + +// Empty default nonintrusive implementation. +template +struct iface_impl { +}; + +// Specialisation for int value type. +template +struct iface_impl : public Base { + int foo() const final + { + return 42; + } +}; + +template +struct iface_impl { +}; + +// Specialisation for int value type. +template +struct iface_impl : public Base { + int bar() const final + { + return 43; + } +}; + +} // namespace tanuki + +TEST_CASE("basics") +{ + using wrap_t = tanuki::wrap; + + wrap_t w(123); + REQUIRE(w->foo() == 42); + + REQUIRE(!std::constructible_from); + + using wrap2_t = tanuki::wrap; + + wrap2_t w2(123); + REQUIRE(w2->bar() == 43); + + REQUIRE(!std::constructible_from); + + // Composite interface. + using wrap3_t = tanuki::wrap>; + + wrap3_t w3(123); + REQUIRE(w3->foo() == 42); + REQUIRE(w3->bar() == 43); + + REQUIRE(!std::constructible_from); +} + +// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while) From daed28c52d7b9c57df34262af1687f157206e6cb Mon Sep 17 00:00:00 2001 From: Francesco Biscani Date: Wed, 15 May 2024 18:47:33 +0200 Subject: [PATCH 2/2] Add docs/tutorial. --- doc/hello_world.rst | 2 +- doc/nonintrusive.rst | 49 +++++++++++++++++++++++++++++++++++++++ doc/tutorials.rst | 1 + doc/utils.rst | 10 ++++++++ tutorial/CMakeLists.txt | 1 + tutorial/nonintrusive.cpp | 40 ++++++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 doc/nonintrusive.rst create mode 100644 tutorial/nonintrusive.cpp diff --git a/doc/hello_world.rst b/doc/hello_world.rst index 0610c7e..0461053 100644 --- a/doc/hello_world.rst +++ b/doc/hello_world.rst @@ -81,7 +81,7 @@ Second, we add to the ``any_iface`` definition an ``impl`` template alias to ind destructors. Note that this is an *intrusive* way of specifying the implementation of an interface. -A non-intrusive alternative is also available, so that it is possible to provide +A :ref:`non-intrusive alternative ` is also available, so that it is possible to provide implementations for existing interfaces without modifying them. And we are done! We can now use ``any_iface`` in the definition of a type-erased diff --git a/doc/nonintrusive.rst b/doc/nonintrusive.rst new file mode 100644 index 0000000..8f1c2aa --- /dev/null +++ b/doc/nonintrusive.rst @@ -0,0 +1,49 @@ +.. _nonintrusive: + +.. cpp:namespace-push:: tanuki + +Non-intrusive interface implementations +======================================= + +In all the examples seen so far, implementations for tanuki interfaces have always +been specified by adding an ``impl`` template alias in the body of the interface class. +This is an *intrusive* approach, in the sense that it requires modifying the definition +of the interface. + +In order to be able to adapt existing object-oriented interfaces without having to modify them +by adding the ``impl`` alias, tanuki also supports a non-intrusive way of specifying +interface implementations. Let us see it in action. + +Consider the following object-oriented interface ``my_iface`` defined in some namespace ``ns``: + +.. literalinclude:: ../tutorial/nonintrusive.cpp + :language: c++ + :lines: 6-15 + +In order to provide a non-intrusive tanuki implementation for ``my_iface``, we need to implement +a partial specialisation of the :cpp:struct:`iface_impl` struct for ``my_iface``: + +.. literalinclude:: ../tutorial/nonintrusive.cpp + :language: c++ + :lines: 17-29 + +Here we are specifying an implementation for all value types ``T``, but, as explained +in previous tutorials, we could also easily provide a partially-specialised implementation, +constrain the implementation only for value types modelling +certain requirements, provide an empty default implementation, etc. + +We are now able to wrap ``my_iface`` in the usual way: + +.. literalinclude:: ../tutorial/nonintrusive.cpp + :language: c++ + :lines: 31-40 + +.. code-block:: console + + The final answer is 42 + +Full code listing +----------------- + +.. literalinclude:: ../tutorial/nonintrusive.cpp + :language: c++ diff --git a/doc/tutorials.rst b/doc/tutorials.rst index e81343a..f3ddb87 100644 --- a/doc/tutorials.rst +++ b/doc/tutorials.rst @@ -11,3 +11,4 @@ Tutorials wrap_reference.rst custom_construct.rst composite_interfaces.rst + nonintrusive.rst diff --git a/doc/utils.rst b/doc/utils.rst index 9748d31..baad29e 100644 --- a/doc/utils.rst +++ b/doc/utils.rst @@ -46,3 +46,13 @@ Utilities This concept detects if :cpp:type:`T` is a type that can be type-erased by a :cpp:class:`wrap`. :cpp:type:`T` must be a non-cv qualified destructible object. + +.. cpp:struct:: template iface_impl + + Non-intrusive interface implementation. + + This class can be partially specialised to specify a non-intrusive implementation for the interface :cpp:type:`IFace`. + See the :ref:`tutorial ` for an example. + + The unspecialised version of this class is an empty trivial structure which disables non-intrusive implementations + for the interface :cpp:type:`IFace`. diff --git a/tutorial/CMakeLists.txt b/tutorial/CMakeLists.txt index b464424..071f028 100644 --- a/tutorial/CMakeLists.txt +++ b/tutorial/CMakeLists.txt @@ -26,4 +26,5 @@ ADD_TANUKI_TUTORIAL(wrap_reference) ADD_TANUKI_TUTORIAL(emplace) ADD_TANUKI_TUTORIAL(compose1) ADD_TANUKI_TUTORIAL(compose2) +ADD_TANUKI_TUTORIAL(nonintrusive) ADD_TANUKI_TUTORIAL(std_function) diff --git a/tutorial/nonintrusive.cpp b/tutorial/nonintrusive.cpp new file mode 100644 index 0000000..08bded8 --- /dev/null +++ b/tutorial/nonintrusive.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include + +namespace ns +{ + +// An existing OO interface. +struct my_iface { + virtual ~my_iface() = default; + virtual int foo() const = 0; +}; + +} // namespace ns + +namespace tanuki +{ + +// Non-intrusive implementation for the ns::my_iface interface. +template +struct iface_impl : public Base { + int foo() const override + { + return 42; + } +}; + +} // namespace tanuki + +int main() +{ + // Define a wrap for ns::my_iface. + using wrap_t = tanuki::wrap; + + wrap_t w1(123); + wrap_t w2(std::string("hello world!")); + + std::cout << "The final answer is " << w1->foo() << '\n'; +}