How to Format a String in C++

String formatting is a common and useful operation that is regularly used in most programming languages. Unfortunately, the formatting solution offered in the C++ Standard Library lacks simplicity and intuitiveness. For that reason, implementing a C-style “printf” type function for string formatting can be a worthy investment as it offers an advantage in terms of convenience over C++ I/O manipulators.

The implementation below makes use of several C++11 features that improve readability and safety concerns that would exist in older legacy code. The most notable one being the variadic template parameter pack that acts as a wrapper around the variadic function used in C-style “printf” methods.

template<typename... Args>
std::string format_string(const std::string& format, Args... args)
{
    const auto size = std::snprintf(nullptr, 0, format.c_str(), args...) + 1;
    const auto buffer = std::make_unique<char[]>(size);

    std::snprintf(buffer.get(), size, format.c_str(), args...);

    return std::string(buffer.get(), buffer.get() + size - 1);
}

Here’s a quick rundown of what is happening in this function:

  1. The size of the char array is determined by getting the return value from std::snprintf. (Note: plus one is added to this value to make room for the null terminator at the end of the array)

  2. A new char array is allocated through the use of a unique pointer to not worry about manually deleting this memory again.

  3. std::snprintf is used to write the formatted string into the char array.

  4. A new string is created from the char array buffer and then returned.

While this function seems to get the job done, there is still one more point missing to fully support C++ specific types. More specifically, this method doesn’t work whenever a std::string object is passed as part of its argument list. To fix this issue there needs to be an additional check to convert a std::string object into a “const char*” so that it gets forwarded as a parameter for std::snprintf. The complete implementation considering this aspect is shown below (Note: this solution uses C++17 features):

template<typename T>
auto convert(T&& t)
{
    if constexpr (std::is_same<std::remove_cv_t<std::remove_reference_t<T>>, std::string>::value)
    {
        return std::forward<T>(t).c_str();
    }
    else
    {
        return std::forward<T>(t);
    }
}

template<typename... Args>
std::string format_string_internal(const std::string& format, Args&& ... args)
{
    const auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...) + 1;
    const auto buffer = std::make_unique<char[]>(size);

    std::snprintf(buffer.get(), size, format.c_str(), std::forward<Args>(args)...);

    return std::string(buffer.get(), buffer.get() + size - 1);
}

template<typename... Args>
std::string format_string(const std::string& format, Args&& ... args)
{
    return format_string_internal(format, convert(std::forward<Args>(args))...);
}

Now this solution may seem like a lot more complex than the previous one, but in reality, there is not a lot that has changed. The most noticeable difference is the inclusion of the “convert” function that checks at compile-time if the parameter passed is a string, and if it is then it returns the C-string given by the “c_str()” method, otherwise, it returns the type itself. Moreover, an additional format function is added to make sure the user does not have to explicitly call the “convert” method whenever formatting a string. Lastly, the use of std::forward is included to ensure perfect forwarding takes place.

Conclusion

C-style “printf” functions provide a simpler and more intuitive interface compared to the C++ standard solution of using I/O manipulators. Hence, the previous implementation for a string formatting function offers a great alternative from what can currently be done using C++ streams. As a side note, libraries like fmt and boost::format also provide great solutions to string formatting needs.

Source Code

How to Create a Random String in C++

Generating a random string in C++ is a simple task that can be easily achieved by the use of rand(); however, this method is not recommended because rand() lacks a distribution engine and the quality of its implementation leaves more to be desired. To combat this issue, the C++ Standard Library provides a set of functions from the Random header that allows users to generate random numbers from well-defined engines and distributions. The implementation of the function that generates a random string will make use of three key features present in the facilities of the Random header.

The elements used being:

The implementation for the random string generator combining all these features together is shown below:

std::string random_string(std::size_t length)
{
    const std::string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    std::random_device random_device;
    std::mt19937 generator(random_device());
    std::uniform_int_distribution<> distribution(0, characters.size() - 1);

    std::string random_string;

    for (std::size_t i = 0; i < length; ++i)
    {
        random_string += characters[distribution(generator)];
    }

    return random_string;
}

As you can see, this function hard codes all the possible characters that will be used to form the random string. Then, it initializes the three needed objects from the Random header by creating a Mersenne Twister engine and a distribution that covers all indexes from the possible characters string. Ultimately, it iterates through the arbitrary length given as a parameter to populate the string with a random character in every iteration.

Conclusion

Avoiding the use of rand() is a piece of advice that does not only apply to create random strings. Instead, it is recommended to use the facilities provided in the Random header that were included in the C++11 Standard Library.

Source Code