1

I need to write a function for parsing JSON object. For convinience I decided to use templates for doing that.

So, my function looks as follows:

template<typename ValueType>
ValueType get(const Json& json);

For each type of values i want to receive from JSON there is a specialization:

template<>
uint8_t get(const Json& json);
template<>
double get(const Json& json);
template<>
std::string get(const Json& json);
...

Now I want to extend this function and add the ability to receive containers of elements. As I understand for achieving that I need to add specialization for every container type and every value type:

template<>
std::vector<uint8_t> get(const Json& json);
template<>
std::vector<std::string> get(const Json& json);
...

It is absolutely not convinient because of N*N complexity. Instead of doing that I want have something like this:

template<typename ValueType>
std::vector<ValueType> get<std::vector<ValueType>>(const Json& json);

or even this:

template<template<typename...> class Container, typename ValueType>
Container<ValueType> get(const Json& json);

But if I try to write such templates, i receive the error:

error: function template partial specialization ‘getstd::vector<_RealType >’ is not allowed std::vector getstd::vector<ValueType>(const Json& json);

How should I refactor the function for achieving my goal?

borune
  • 548
  • 5
  • 21

3 Answers3

2

You might use tag dispatcher helper (so overload instead of specialization):

template <typename T>
struct tag{};

uint8_t get_helper(tag<uint8_t>, const Json& json);
double get_helper(tag<double>, const Json& json);
std::string get_helper(tag<std::string>, const Json& json);

template <typename ValueType>
std::vector<ValueType> get_helper(tag<std::vector<ValueType>>, const Json& json)
{
    // ... use get_helper(tag<ValueType>{}, sub_json);
}


template<typename ValueType>
ValueType get(const Json& json)
{
    return get_helper(tag<ValueType>{}, json);
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • template should indeed go in header file... unless you know each of its instantiation, but then you can have `std::vector get_helper(tag>, const Json& json); /*..*/` and possibly implement them via a call to a template function. – Jarod42 Feb 25 '22 at 16:32
  • so, if I understand you correctly, i should have implementation for each value type i want support and for each contaner, right? – borune Feb 25 '22 at 17:39
  • Yes, and template allows to minimize code duplication. – Jarod42 Feb 25 '22 at 18:57
1

This answer assumes c++17 for variable templates.

Write a type template, and partially specialize that type template. Put the parse functions you need as operator() members inside the partial type template specializations. Declare get as a variable template that instantiate that type template.

template<typename Ret>
struct get_impl;

template<typename Elem>
struct get_impl<std::vector<Elem>> {
    auto operator()(const Json&) -> std::vector<Elem>; 
};

template<>
struct get_impl<std::string> {
    auto operator()(const Json&) -> std::string;
};

template<typename Ret>
inline constexpr auto get = get_impl<Ret>{};

BTW, this is similar to how one would implement a customization point object.

Zuodian Hu
  • 979
  • 4
  • 9
1

IMO use of templates can be avoided.
Change code from return value to pass by reference and it becomes easy and clean:

void get(const Json& json, uint8_t& retVal);
void get(const Json& json, double& retVal);
void get(const Json& json, std::string& retVal);

To cover std::vector flavors you can use this templete:

template<typename T>
void get(const Json& json, std::vector<T>& retVal)
{
    if (json.isArray()) {
       auto jarray = json.toArray();
       retVal.resize(jarray.size());
       for (size_t i = 0; i < retVal.size(); ++i) {
           get(jarray[i], retVal[i]);
       }
    }
}

If you need template anyway approach above could be used to solve your problem using only default implementation of template:

template<typename ValueType>
ValueType get(const Json& json);
{
    ValueType retVal;
    get(json, retVal); // use overloads from above
    return retVal;
}

This way default template implementation does everything what is needed. Extending is simple too.

Marek R
  • 32,568
  • 6
  • 55
  • 140
  • 1
    Vote down is not mine, but ur solution does not resolve he problem, because I still will need `get` overload for each value type, i.e. for std::vector, std::vector – borune Feb 25 '22 at 15:58
  • 1
    @borune: It resolve the issue as mine. MarekR use type as reference directly whereas I use `tag`. That solution add the requirement that `ValueType` is default constructible. though. – Jarod42 Feb 25 '22 at 16:25
  • @borune thanks for the hint what you are consider as a problem, answer improved. – Marek R Feb 25 '22 at 16:46
  • It was me. I had the same complaint as OP. Ive taken it back after your edit. – Zuodian Hu Feb 25 '22 at 17:41
  • @MarekR i see your edit, but unfortunately it does not satisfy my requirements. JSON is an example and actually any object can be instead of JSON. for example, PyObject or something serialized. But ur solution means that this object has `isArray` and `toArray` methods, what is wrong in general. . – borune Feb 25 '22 at 18:00
  • @MarekR and anyway with your solution i will need have implementations for each value type i want support and for each contaner, i.e. `template> void get(const Json& json, std::vector& retVal); template> void get(const Json& json, std::vector& retVal)`. but my main goal is to avoid doing that – borune Feb 25 '22 at 18:09
  • @borune nope you do not have to have multiple implementation for each vector argument. First and second listing combined covers that. Anyway looks like you should clarify your requirements in a question. It is not clear why you need specializations. Maybe it would be nice specify json library and to what conversion should be done. Explain also why you think you need specializations, why default implementation fails (you didn't provide default implementation too). – Marek R Feb 25 '22 at 18:21