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?
CodePudding user response:
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);
}
CodePudding user response:
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.
CodePudding user response:
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.