c++c++20c++-conceptsnlohmann-json

Constraining a template specialization to a concept calling a function template?


I have some reflective visitor function for a struct that looks like this:

namespace ns {

struct something {
  int x;
  int y;
};

template <typename V>
void visit_members_adl(V&& visitor, const something& instance) {
  visitor("x", instance.x);
  visitor("y", instance.y);
}

}  // namespace ns

I want to allow any class which has this visit_members_adl free function defined in its same namespace can be automatically serialized to JSON, using nlohmann's JSON library. I did this:

#include "json.hpp"

// placeholder concept
template <typename T>
concept Visitable = requires(T t) {
  { t } -> std::convertible_to<ns::something>;
};

namespace nlohmann {

template <Visitable T>
struct adl_serializer<T> {
  static void to_json(nlohmann::json& j, const T& t) {
    auto visitor = [&j](const char* name, const auto& member) {
      j[name] = member;
    };
    visit_members_adl(visitor, t);
  }
};

}  // namespace nlohmann

int main() {
  ns::something s{1, 2};

  if constexpr (Visitable<ns::something>) {
    nlohmann::json j;
    printf("Visitable = true: %s\n", j.dump().c_str());
  } else {
    printf("Visible = false\n");
  }

  return 0;
}

And it works! But obviously, it's too narrow a constraint.

Is there a way to define this Visitable concept as a broader constraint that captures the idea "visit_members_adl(visitor, t) is a valid expression for any visitor with the right signature"? Or am I thinking about this the wrong way from the jump?


Solution

  • It looks like this declaration

    template <typename T>   
    concept Visitable = requires(T t) { 
       visit_members_adl([](const char*, const auto&) {}, t); 
    };
    

    does what you want.