c++argument-dependent-lookupnlohmann-json

Hidden friend to_json function unexpectedly resolves for shared_ptr


The code below (Goldbolt) compiles and runs (on both gcc and clang) and does what I would hope. But I'm surprised! I expected to have to use an adl_serializer specialisation (as opposed to the hidden friend here) for it to be able to find the to_json/from_json functions, as the Example class is hidden inside a std::shared_ptr.

So the question is, is this expected (and why?), or a bug in the compilers that might break my code when it gets fixed.

#include <memory>
#include <nlohmann/json.hpp>

struct Example {
    int a;
    int b;

    Example(int a, int b) : a(a), b(b){};

    Example(Example&&) = delete;
    Example& operator=(Example&&) = delete;
    Example(Example&) = delete;
    Example& operator=(Example&) = delete;

    friend void to_json(nlohmann::json& j, const std::shared_ptr<Example>& p) {
        j = {{"a", p->a}, {"b", p->b}};
    }

    friend void from_json(const nlohmann::json& j,
                          std::shared_ptr<Example>& p) {
        p = std::make_shared<Example>(j.at("a").get<int>(),
                                       j.at("b").get<int>());
    }
};

int main() {
    auto j = R"({ "a" : 1, "b" : 2 })"_json;

    auto c = j.get<std::shared_ptr<Example>>();

    auto j2 = nlohmann::json(c);
}

Solution

  • A friend function definition not declared elsewhere is declared in the same namespace as the class it is defined in.

    There are two reasons your to_json and from_json are found.

    The first, and simplest reason is that Example is in the global namespace, so lookup will reach there if it doesn't find a match elsewhere.

    The second reason is that adl looks at the namespace that Example is in.

    1. For arguments whose type is a class template specialization, in addition to the class rules, the following types are examined and their associated classes and namespaces are added to the set

      a. The types of all template arguments provided for type template parameters (skipping non-type template parameters and skipping template template parameters)

    (from cppreference)