Variadic strcat for c++17

Posted on

Problem

On some video from CPPcon, some one said that there should be variadic overload for operator+ as it can solve allocation when there are multiple + with the containers like std::string. So I just gave it a try, but I am not seeing a major performance boost. Are there any other ways to improve this?

#include <string>
#include <string_view>
#include <string.h>

using string_type = std::string_view;

inline std::string::size_type total_len(const string_type& str) {
    return str.length();
}

template<typename... String>
inline std::string::size_type total_len(const string_type& str, const String... strs) {
    return str.length() + total_len(strs...);
}

inline void append(size_t l, std::string& str1, const string_type& str2) {
    memcpy(str1.data() + l, str2.data(), str2.size());
    l += str2.size();
}

template<typename... String>
void append(size_t l, std::string& str1, const string_type& str2, String... strs) {
    void * p1 = str1.data() + l;
    const void * p2 = str2.data();
    memcpy(p1,p2, str2.size());
    l += str2.size();
    append(l, str1, strs...);
}

template<typename... String>
std::string StrCat(const string_type& str1, const string_type& str2, const String... strs) {
    std::string answer(total_len(str1, str2, strs...), 0);
    append(0, answer, str1, str2, strs...);
    return answer;
}

int main()
{
  StrCat("Hello, ","name","!n");
}

Solution

Benchmarking is a tricky business. I’ll focus more on the code part, you tell me if you get an improvement on the performance part too.

Containers or c-style strings?

with containers like std::string

If you do want to work with containers, you can simplify your code a lot and avoid the use of std::string_view, which I believe triggers a good amount of conversions in your code.

For instance, your total_len function can be written as concisely as:

template<typename... String>
std::string::size_type total_len(const String&... strs) {
    return (strs.size() + ...);
}

Besides, I’m a bit skeptical about using memcpy on data() and such when you don’t really need to. You won’t beat std::string implementations that way, and you won’t bring much flexibility either because at the end of the line, the product is still a std::string, not a newly malloced char*. But if a small error has gotten into your code, you’ll be the guy who reinvented the wheel one time too many again.

Folding the right left way

Fold expressions are by the way more powerful than you seem to think. You don’t have to be explicit about recursion. If you hang on to your std::strings, you can write a left folding in three lines:

template <typename... String>
void my_strcat_impl(String&&... strs) {
   (... += strs);
}
// <=> (((str1 += str2) += str...) += strN)

That means that the complete working example is rather short too:

#include <string>   
using namespace std::string_literals;

template<typename... String>
std::string::size_type total_len(const String&... strs) {
    return (strs.size() + ...);
}

template <typename... String>
void my_strcat_impl(String&&... strs) {
    (... += strs);
}

template <typename... String>
std::string my_strcat(const String&... strs) {
    std::string result;
    result.reserve(total_len(strs...));
    my_strcat_impl(result, strs...);
    return result;
}

int main()  {
    auto test = my_strcat("Hello, "s,"name"s,"!n"s);
}

Leave a Reply

Your email address will not be published. Required fields are marked *