1

I'm writing a json library that has the same usage as nlohmann/json. But I'm having trouble understanding nlohmann's get() function. So I implemented a get() myself, but I think that my method is not very good, do you have any good solutions or suggest?

#include <vector>
#include <iostream>
#include <vector>
#include <string>
#include <map>

using namespace std;

bool func(bool) { return 1; }
int func(int) { return 2; }
double func(double) { return 3; }
string func(string&) { return string("4"); }
vector<int> func(vector<int>&) { return { 5 }; }
map<int, int> func(map<int, int>&) {
    map<int, int>a;
    a.emplace(6, 6);
    return a;
}

template <typename T>
T get() {
    static T t;
    return func(t);
}

int main() {
    cout << get<bool>();
    cout << get<int>();
    cout << get<double>();
    cout << get<string>();
    cout << get<vector<int>>()[0];
    cout << get<map<int, int>>()[6];
}
qingl
  • 71
  • 7
  • 2
    Any reason why you need those `func`s? If it's all about the template selecting an appropriate overload then I'd skip the implementation of the base template and turn all those `func`s into specialisations. – Aconcagua Mar 30 '22 at 06:04
  • 1
    Side note: About [`using namespace std`](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice). As you talk about a library that gets special relevance, as you'd not only spoil your own global namespace, but the one of everybody using your library (at least if placed into a header). – Aconcagua Mar 30 '22 at 06:07
  • 1
    What's not "good" about your solution? – Passer By Mar 30 '22 at 06:22
  • 1
    If you implement the json variant using `std::variant`, `get` becomes a simple wrapper. – chris Mar 30 '22 at 06:31

1 Answers1

3

Your way works, but

  • requires default constructible types (so no void, reference, ...),
  • types which do "nothing" (a RAII object using global mutex would be problematic for example). Even a log might be strange.
  • You should care about conversion/promotion with overloading resolution (get<char> is ambiguous from your types, get<void(*)()> would call func(bool), but fortunately would fail to compile too because of return type)

As alternative:

  • as your set of types seems limited and no extensible,

    • you might used if constexpr:

      template <typename T>
      T get() {
          if constexpr (std::is_same_v<T, bool>) {
              return true;
          } else if constexpr (std::is_same_v<T, int>) {
              return 42;
          } else if constexpr (std::is_same_v<T, double>) {
              return 4.2;
          } else if constexpr (std::is_same_v<T, std::string>) {
              return "Hello world";
          } else if constexpr (std::is_same_v<T, std::vector<int>>) {
              return {4, 8, 15, 16, 23, 42};
          } else if constexpr (std::is_same_v<T, std::map<int, int>>) {
              return {{1, 2}, {3, 4}};
          } else {
              static_assert(always_false<T>::value);
          }
      }
      
    • or (full) specialization:

      template <typename T> T get(); // No impl, to specialize
      
      template <> bool get() { return true; }
      template <> int get() { return 42; }
      template <> double get() { return 4.2; }
      template <> std::string get() { return "Hello world"; }
      template <> std::vector<int> get() { return {4, 8, 15, 16, 23, 42}; }
      template <> std::map<int, int> get() { return {{1, 2}, {3, 4}}; }
      
  • Reusing your idea of dispatching but with a tag. (that allows extensible set of types, and template implementation)

    template <typename T> struct tag {};
    
    bool func(tag<bool>) { return true; }
    int func(tag<int>) { return 42; }
    double func(tag<double>) { return 4.2; }
    std::string func(tag<std::string>) { return string("Hello world"); }
    std::vector<int> func(tag<std::vector<int>>) { return {4, 8, 15, 16, 23, 42}; }
    std::map<int, int> func(tag<std::map<int, int>>) { return {{1, 2}, {3, 4}}; }
    
    #if 0 // Allow extension and template
    template <typename T>
    UserTemplateType<T> func(tag<UserTemplateType<T>>) { return {}; }
    #endif
    
    template <typename T>
    T get() {
        return func(tag<T>{});
    }
    
Jarod42
  • 203,559
  • 14
  • 181
  • 302