Reference semantics

Reference semantics#

In the first tutorial, we mentioned how one of the reasons to prefer type-erasure over traditional object-oriented programming is that with type-erasure we can implement value semantics, rather than being forced into pointer/reference semantics.

There are however situations in which pointer/reference semantics might be preferrable. For instance, the motivating example for the development of reference semantics in tanuki was the necessity to support large computational graphs featuring a high degree of internal repetition in the heyoka library.

Reference semantics support in tanuki is activated by setting the config::semantics config option to wrap_semantics::reference. When reference semantics is activated, the wrap class will employ internally a shared pointer to store the type-erased value, and copy/move/swap operations on a wrap will behave like the corresponding operations on a shared pointer - that is, they will manipulate references to the internal value, rather than the value itself.

Additionally, there are a couple of extra functions in the API that are available only when using reference semantics.

The first one, wrap::copy(), is used to make a deep-copy of a wrap (that is, it enforces the creation of a copy of the internal value, rather than creating a new shared reference to it).

The second one, wrap::same_value(), can be used to detect if two wrap objects share ownership of the same value.

Let us see a simple example of reference semantics in action. We begin with our usual, super-simple interface and its implementation:

template <typename Base, typename Holder, typename T>
struct any_iface_impl : public Base {
};

struct any_iface {
    template <typename Base, typename Holder, typename T>
    using impl = any_iface_impl<Base, Holder, T>;
};

We introduce a wrap type employing reference semantics:

    using any_wrap = tanuki::wrap<any_iface, tanuki::config<>{.semantics = tanuki::wrap_semantics::reference}>;

And we create a wrap instance storing a std::string:

    any_wrap w1(std::string("hello world"));

Let us now make a copy of w1:

    auto w2 = w1;

As explained earlier, this operation will not copy the string inside w1, rather it will create a new reference to it. We can confirm that this indeed is the case by comparing the addresses of the values stored in w1 and w2:

    std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr<std::string>(w1) << '\n';
    std::cout << "Address of the value wrapped by w2: " << tanuki::value_ptr<std::string>(w2) << '\n';
Address of the value wrapped by w1: 0x606000000038
Address of the value wrapped by w2: 0x606000000038

We can also invoke the wrap::same_value() function for further confirmation:

    std::cout << "Do w1 and w2 share ownership? " << same_value(w1, w2) << '\n';
Do w1 and w2 share ownership? true

If, on the other hand, we use the wrap::copy() function, we will be performing a copy of the string stored in w1:

    auto w3 = copy(w1);

    std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr<std::string>(w1) << '\n';
    std::cout << "Address of the value wrapped by w3: " << tanuki::value_ptr<std::string>(w3) << '\n';

    std::cout << "Do w1 and w3 share ownership? " << same_value(w1, w3) << '\n';
Address of the value wrapped by w1: 0x606000000038
Address of the value wrapped by w3: 0x606000000098
Do w1 and w3 share ownership? false

Full code listing#

#include <ios>
#include <iostream>
#include <string>

#include <tanuki/tanuki.hpp>

template <typename Base, typename Holder, typename T>
struct any_iface_impl : public Base {
};

struct any_iface {
    template <typename Base, typename Holder, typename T>
    using impl = any_iface_impl<Base, Holder, T>;
};

int main()
{
    using any_wrap = tanuki::wrap<any_iface, tanuki::config<>{.semantics = tanuki::wrap_semantics::reference}>;

    any_wrap w1(std::string("hello world"));

    auto w2 = w1;

    std::cout << std::boolalpha;

    std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr<std::string>(w1) << '\n';
    std::cout << "Address of the value wrapped by w2: " << tanuki::value_ptr<std::string>(w2) << '\n';

    std::cout << "Do w1 and w2 share ownership? " << same_value(w1, w2) << '\n';

    auto w3 = copy(w1);

    std::cout << "Address of the value wrapped by w1: " << tanuki::value_ptr<std::string>(w1) << '\n';
    std::cout << "Address of the value wrapped by w3: " << tanuki::value_ptr<std::string>(w3) << '\n';

    std::cout << "Do w1 and w3 share ownership? " << same_value(w1, w3) << '\n';
}