0

I wrote a very compact, limited Json Encoder with C++17 function templates:

namespace {
    using std::string;

    template<typename T>
    string toString(T&& t){
        if constexpr (std::is_arithmetic_v<std::decay_t<T>>)
            return std::to_string(t);
        else if constexpr (std::is_same<string, typename std::remove_reference<T>::type>::value)
            return "\"" + t + "\"";
        else
            return "\"" + string(t) + "\"";            
    }

    template <typename Arg1, typename Arg2>
    std::string append(Arg1&& arg1, Arg2&& arg2) {
        return toString(arg1) + ":" + toString(arg2);
    }

    template <typename Arg1, typename Arg2, typename... Args>
    std::string append(Arg1&& arg1, Arg2&& arg2, Args&&... moreArgs) {
        return toString(arg1) + ":" + toString(arg2) + "," + append(moreArgs...);
    }
}

template <typename... Args>
static std::string encodeJson(Args&&... args) {
    return "{" + append(args...) + "}";
}

It's part of a specific project, so it's fine if only cases are supported that are actually used. I can simply create JSON messages by using keys and values, as alternating parameters:

auto json = encodeJson("key1", "value1", "key2", 2);

However, now I do need nested JSON objects. I am trying to extend the code above, so I am able to do something like:

encodeJson("key", "value", "nested", std::make_tuple("subkey1","subval1","subkey2",3));

I tried by adding the following template function:

template<typename ... Args>
    std::string toString(std::tuple<Args...> t) {
        std::string s = std::apply(append, t);
        return "{" + s + "}";
    }

However, I have issues where to put it. If I put if before the definitions of append, then std::apply cannot resolve append. If I put it after the definitions of append, it is not used by append.

Apparently I am trying to accomplish indirect/mutual recursion with two template functions. Is there a solution for this? (preferrably preserving the simplicity of the code...)

General comments on the code are also appreciated, of course! I am not an expert in template programming.

philipp
  • 1,745
  • 1
  • 14
  • 25

1 Answers1

1

You have to declare the function, then define it later. That is no different from any other function, template or not.

template<typename ... Args>
    std::string toString(std::tuple<Args...> t);

// place definitions of append() here

template<typename ... Args>
    std::string toString(std::tuple<Args...> t) {
        std::string s = std::apply(append, t);
        return "{" + s + "}";
    }
j6t
  • 9,150
  • 1
  • 15
  • 35
  • Then I get `no matching function for call to ‘apply(, std::tuple&)’` from gcc. Anyways, good to know it should work in general. Trying to make the `std::apply` more explicit.. – philipp Feb 27 '22 at 09:48
  • Got it working, by additionally using the workaround from https://stackoverflow.com/questions/687490/how-do-i-expand-a-tuple-into-variadic-template-functions-arguments to call templated functions with apply. – philipp Feb 27 '22 at 10:29