[Solution] Riddle – The Shared View

Previous week I published the riddle “The Shared View”. Today we’ll get into this riddle details and will explain its solution.

General Overview

The code starts with two helper structures: `fixed_string` and `overloaded`. Let’s take a look over `fixed_string` structure:

fixed_string

template<int N> struct fixed_string { 
    constexpr fixed_string(char const (&s)[N]) { 
        std::copy_n(s, N, this->elems);
        elems[N] = 0;
    }
   
    constexpr operator const char*() const { return elems; }
    constexpr operator std::string_view() const { return elems; }

    char elems[N + 1]; 
};

This structure allowing us to pass a char* parameter as a non-type template parameter, without the need to hold an external const char pointer to the string. It’s currently a limitation in compilers, but it should be resolved in the future.

The way it works is by letting you accept as a non-type template parameter a class object, which can be constructed out of a const char array. Which means that you can pass a compile time char array directly as a parameter to the template.

overloaded

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

This`overloaded` structure is simply accepts a variadic template types, and inherit from them. The second line is used as a hit to the compiler for type deduction. The second line won’t be needed in future compilers (at least newer than the ones I tested it on) due to CTAD (class template argument deduction) in C++20 standard (for a further read about it, visit cppreference).

In this riddle this structure will be used as a way to pass to std::visit function multiple function handlers for different types. Note: this structure is a complete copy from cppreference – std::visit example.

`A` structute

template <typename T>
class A {
private:
    template<typename T1>
    using sptr = std::shared_ptr<T1>;
    using p = sptr<T>;
     
public:
    template <typename... Args>
    p create(Args&&... args) {
        return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
    }
    template <typename T1>
    sptr<T1> get(p inst_p, T1* t1) {
        return sptr<T1>(inst_p, t1);
    }
};

The `A` structure is responsible for generating new std::shared_ptr instances. But here we have a nuance: the `get` function uses a less known std::shared_ptr constructor named “the aliasing constructor“.

`B` structure

struct B {
    using at = std::variant<int*, double*>;
    int a, b;
    double c;
     
    at get(const std::string_view& str) {
        static const std::unordered_map<std::string_view, at> m {
            { "a", &a },
            { "b", &b },
            { "c", &c }
        };
        return m.at(str);
    }
     
private:
    template <fixed_string Str>
    struct get_t {};
    static constexpr const char a_str[] = "a";
    template <>
    struct get_t <a_str> { using type = decltype(a); };
     
    static constexpr const char b_str[] = "b";
    template <>
    struct get_t <b_str> { using type = decltype(b); };
     
    static constexpr const char c_str[] = "c";
    template <>
    struct get_t <c_str> { using type = decltype(c); };
     
public:
    template <fixed_string Str>
    using get_type = typename get_t<Str>::type;
};

Here we can see a factory design pattern that is used to get closer to the reflection design pattern.

The function `get` creates a static map that holds a mapping between fields’ names to their corresponding actual address. The function accepts a string_view parameter, and returns the matched field address on the map (well it’s a little bit of a lie because it actually returns a std::variant instance that holds the specific pointer type of the desired data member).

This class holds also some inner (private) structures that are used to get a field type given its name. The type `get_type` is the shortcut for getting the relevant structure inner type. This technique is use a lot within the standard, for example: std::enable_if & std::enable_if_t.

`MyAB` structure

class MyAB {
private:
    [[no_unique_address]] A<B> a;
    decltype(a.create()) inst;
     
    auto get(auto& t) { return a.get(inst, &t); }
     
public:
    MyAB() : inst(a.create()) {}
     
    auto get() { return inst; }
     
    template <fixed_string VN>
    auto get_v() {
        std::shared_ptr<B::get_type<VN>> ret;
        std::visit(overloaded {
            [this, &ret](B::get_type<VN>* v) {
                ret = this->get(*v);
            },
            [](auto*) {}
        }, inst->get(VN));
        return ret;
    }
     
    void reset(decltype(inst)::element_type* p) { inst.reset(p); }
};

This structure is the combination between `A` & `B` structures. It’s holding (using [[no_unique_address]] attribute) a generator `A` class instance, and an actual generated std::shared_ptr that the generator will create using its `create` function (which is std::shared_ptr<B>).

`get_v` function is the most interesting function here. It accepts using non-type template parameter of fixed_string named VN. It uses it to get the actual type in B structure, that matches the VN name. This type will be used to create a std::shared_ptr instance of this type. We can see this in the first line inside this function:

std::shared_ptr<B::get_type<VN>> ret;

Now we want to get a pointer to the specific data member of our shared_ptr<B> instance named `inst`. The issue is that this inst->get function returns a std::variant type, so we have to use std::visit to access it. Moreover we can’t use a single access function with auto* param (unless we use `if constexpr` inside), so I decided to use the `overloaded` structure, and separate the interesting type from the other potential types.

[this, &ret](B::get_type<VN>* v) {
    ret = this->get(*v);
}

Inside this lambda expression we are accessing `get` function inside `MyAB` instance. This `get` function is actually a wrapper for A<B> generator instance’s `get` function, which returns a shared_ptr instance that constructed using the aliasing constructor, as mentioned before. Now all we left to do in this function is to return the `res` variable.

Question #1 Solution

int main()
{
    MyAB b1, b2;
    auto t1 = b1.get_v<"c">();
    auto t2 = b1.get_v<"a">();
     
    auto t3 = b2.get_v<"a">();
    auto t4 = b2.get_v<"c">();
     
    *t1 = 1.2;
    *t2 = 3;
     
    *t3 = 4;
    *t4 = 5.6;
    std::cout << *t1 << *t2 << *t3 << *t4 << "\n";

    return 0;
}

Yes, the code will compile and the output is: “5.6445.6”. Something seems a little bit wring around here. We have two separated MyAB instances, so why they affect each other?

The answer is hidden within a small keyword that created a huge bug. Inside `B` structure, we have a function named `get`. This function holds a static map which points to the specific, current instance, data members. This means that any new instance of this class, will return pointers to the first instance’s data members. The solution is to make this map a non-static map inside this function, or holding it as a data member that initialized on the constructor, in order to save initialization time in every call to this function.

After solving this issue the output is “12.345.6”.

Question #2 Solution

int main()
{
    MyAB b1, b2;
    auto t1 = b1.get_v<"c">();
    auto t2 = b1.get_v<"a">();
     
    auto t3 = b2.get_v<"a">();
    auto t4 = b2.get_v<"c">();
     
    *t1 = 1.2;
    *t2 = 3;
     
    *t3 = 4;
    *t4 = 5.6;
    std::cout << *t1 << *t2 << *t3 << *t4 << "\n";
    
    // Addition
    b1.reset(nullptr);
    t1.reset();
    t2.reset();
    std::cout << *t3 << *t4;

    return 0;
}

In this addition we are expanding the original bug, and make it more visible. The actual result here is an access violation. The first `B` structure instance is completely released (because all the std::shared ptr that hold a pointer to it got released and now the static map will return pointers to a memory location that we no longer own.

Question #3 Solution

int main()
{
    MyAB b1, b2;
    auto t1 = b1.get_v<"c">();
    auto t2 = b1.get_v<"a">();
     
    auto t3 = b2.get_v<"a">();
    auto t4 = b2.get_v<"c">();
     
    *t1 = 1.2;
    *t2 = 3;
     
    *t3 = 4;
    *t4 = 5.6;
    std::cout << *t1 << *t2 << *t3 << *t4 << "\n";

    // Addition
    b1.reset(b2.get().get());
    std::cout << *t1 << *t2 << *t3 << *t4 << "\n";
 
    return 0;
}

Now everything might seems ok, but actually we just got a new fresh bug, by using the given api of MyAB wrongly. b1.reset will pass a new pointer to the inner std::shared_ptr to manage. But the pointer that we pass here is already managed by b2 inner std::shared_ptr instance. So everything will function in a good way, untill the second destructor of the std::shared_ptr that holds this pointer will be called, and then we’ll get an error saying that we are trying to release a pointer that is already been released.

Summarize

std::shared_ptr aliasing constructor is a dangerous tool, that any usage of it must be coupled with documentation, due to the rare case that it might help with them. Also, the static keyword is dangerous as well, and should be considered carefully when in use. Hope this riddle refreshed some contents for you, or help you to get a better understanding of the topics that were being used here. Until next time, don’t be afraid of C++, you’ll get used to it one day 😉

You can now follow this blog for updates also on discord and on telegram channel.

One thought on “[Solution] Riddle – The Shared View

Leave a comment