Skip to content

Commit

Permalink
Merge pull request #60 from bluescarni/pr/nonintrusive
Browse files Browse the repository at this point in the history
Noniontrusive testing and docs
  • Loading branch information
bluescarni committed May 15, 2024
2 parents b561c3f + daed28c commit 220078d
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 1 deletion.
2 changes: 1 addition & 1 deletion doc/hello_world.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <nonintrusive>` 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
Expand Down
49 changes: 49 additions & 0 deletions doc/nonintrusive.rst
Original file line number Diff line number Diff line change
@@ -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++
1 change: 1 addition & 0 deletions doc/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ Tutorials
wrap_reference.rst
custom_construct.rst
composite_interfaces.rst
nonintrusive.rst
10 changes: 10 additions & 0 deletions doc/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename IFace, typename Base, typename Holder, typename T> 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 <nonintrusive>` 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`.
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
89 changes: 89 additions & 0 deletions test/test_nonintrusive.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include <concepts>

#include <tanuki/tanuki.hpp>

#include <catch2/catch_test_macros.hpp>

// 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 <typename Base, typename Holder, typename T>
struct impl {
};
};

} // namespace ns

namespace tanuki
{

// Empty default nonintrusive implementation.
template <typename Base, typename Holder, typename T>
struct iface_impl<ns::my_iface, Base, Holder, T> {
};

// Specialisation for int value type.
template <typename Base, typename Holder>
struct iface_impl<ns::my_iface, Base, Holder, int> : public Base {
int foo() const final
{
return 42;
}
};

template <typename Base, typename Holder, typename T>
struct iface_impl<ns::my_iface2, Base, Holder, T> {
};

// Specialisation for int value type.
template <typename Base, typename Holder>
struct iface_impl<ns::my_iface2, Base, Holder, int> : public Base {
int bar() const final
{
return 43;
}
};

} // namespace tanuki

TEST_CASE("basics")
{
using wrap_t = tanuki::wrap<ns::my_iface>;

wrap_t w(123);
REQUIRE(w->foo() == 42);

REQUIRE(!std::constructible_from<wrap_t, long>);

using wrap2_t = tanuki::wrap<ns::my_iface2>;

wrap2_t w2(123);
REQUIRE(w2->bar() == 43);

REQUIRE(!std::constructible_from<wrap2_t, long>);

// Composite interface.
using wrap3_t = tanuki::wrap<tanuki::composite_iface<ns::my_iface, ns::my_iface2>>;

wrap3_t w3(123);
REQUIRE(w3->foo() == 42);
REQUIRE(w3->bar() == 43);

REQUIRE(!std::constructible_from<wrap3_t, long>);
}

// NOLINTEND(cert-err58-cpp,misc-use-anonymous-namespace,cppcoreguidelines-avoid-do-while)
1 change: 1 addition & 0 deletions tutorial/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
40 changes: 40 additions & 0 deletions tutorial/nonintrusive.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include <iostream>
#include <string>

#include <tanuki/tanuki.hpp>

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 <typename Base, typename Holder, typename T>
struct iface_impl<ns::my_iface, Base, Holder, T> : public Base {
int foo() const override
{
return 42;
}
};

} // namespace tanuki

int main()
{
// Define a wrap for ns::my_iface.
using wrap_t = tanuki::wrap<ns::my_iface>;

wrap_t w1(123);
wrap_t w2(std::string("hello world!"));

std::cout << "The final answer is " << w1->foo() << '\n';
}

0 comments on commit 220078d

Please sign in to comment.