Search code examples
c++variadic-functionsparameter-pack

parameter packs parameter type


I have modified the sample from https://en.cppreference.com/w/cpp/language/parameter_pack to save the string in a variable. My code

#include <string>

void tprintf(std::string& str, const std::string& format) 
{
    str += format; 
}

template <typename T, typename... Targs>
void tprintf(std::string& str, const std::string& format, T arg, Targs ...Fargs)
{
    for ( int i = 0; i < format.size(); i++ )
    {
        if ( format.at(i) == '%' )
        {
            if (format.at(i + 1) == 'd') 
            {
                std::cout << "== 'd' -variable = " << arg << std::endl;
                str += std::to_string(arg);
            }
            else if (format.at(i + 1) == 's') 
            {
                std::cout << "== 's'" << std::endl;
                str += arg;
            }
            tprintf(str, (i + 2 < format.size() ? format.substr(i + 2) : ""), Fargs...);
            break;  
        }
        str += format.at(i);
    }
}

int main()
{
    std::string str;
    int age = 24;
    std::string name("Hugo");
    tprintf(str, "Name: %s, age: %d years\n", name, age);
    std::cout << "result = " << str << std::endl;
}

When compile the code I get the following error:

error: no matching function for call to ‘to_string(std::__cxx11::basic_string<char>&)’
                 str += std::to_string(arg);

I thought the parameter pack expands to

  1. tprintf(std::string&, const std::string&, std::string, int) <- arg is std::string
  2. tprintf(std::string&, const std::string&, int) <- arg is int and I can call std::to_string(arg) But it seems that arg has another type than int (std::string or something else)? When I remove the std::to_string call I get some cryptic characters.

How can I convert the value for the age to a string and append it to str?


Solution

  • tprintf(std::string&, const std::string&, std::string, int) <- arg is std::string

    Correct, therefore, here:

     str += std::to_string(arg);
    

    arg is a std::string, and there is no such std::to_string overload.

    No matter what type T is, the resulting template must be valid C++ code. Even if the corresponding formatting character is d, everything in the function must still be valid C++ code.

    In general, attempting to implement C-style printf formatting, in type-safe C++ doesn't make much sense. You already know what needs to be formatted simply by the virtue of the fact that you know the type of the corresponding parameter.

    The only reason you have different formatting specifiers in C-style printf strings, such as %s or %d, is because this C function has no clue, whatsoever, what is getting to passed to it, as a parameter, so it relies on the actual formatting specifier to know what it is.

    This obviously isn't the case with C++. Your formatting specifier can be just a %, by itself, with nothing else. You know exactly what parameter gets passed in. Therefore, it's much simpler to merely define three overloaded functions:

    template <typename... Targs>
    void tprintf(std::string& str, const std::string& format, int arg, Targs ...args)
    {
       // Just the code that formats an int
    }
    
    template <typename... Targs>
    void tprintf(std::string& str, const std::string& format, const std::string &arg, Targs ...args)
    {
       // Just the code that formats a std::string
    }
    
    void tprintf(std::string& str, const std::string& format)
    {
       // Nothing more to format, just adds everything else in "format" to "str".
    }
    

    In the first two cases, all that needs to happen is to search format for the first % place-holder by itself, copy everything before prior to it into str, followed by the formatted parameter, and then recursively re-invoke tprintf with the remaining args, and what's left in the format.

    P.S. Using forwarding references, "Targs && ...args", together with std::forward, will be a follow-up tweak.