Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Noniontrusive testing and docs #60

Merged
merged 2 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
}