Search code examples
c++c++11templatesvariadic-functions

how to avoid implicit conversion of arguments leading to infinite recursion?


I have a formatting method that I adapted from this example.

I have reduced it to just function calls and a print

It works when the formatting string (first argument) is a const char *

#include <stdio.h>
#include <string>

// base
std::string format(const char *s)
{
    printf("** %s\n",s);
    return "";
}
// recursive, should parse each argument and print the formatted string
template<typename T, typename... Args>
std::string format(const char *s, T value, Args... args)
{
    printf("** %s\n",s);  // dummy
    return "";
}

int main()
{
   format("foo");   
   printf("yay!\n");
}

now I'd like to pass a std::string to format, I have to do:

std::string s = "foo";
format(s.c_str());

I'd like to do

format(s);

So I have added this

// recursive
template<typename... Args>
std::string format(const std::string &s,Args... args)
{
  return format(s.c_str(), args...);
}

But when I pass the string directly as std::string it crashes. Debugging shows an infinite recursion. Difficult to debug templates with std::string constructions, but my guess is that

return format(s.c_str(), args...);

calls itself, because const char * implicitly converts as a std::string.

Here's the full non-working example:

#include <stdio.h>
#include <string>

// recursive
template<typename... Args>
std::string format(const std::string &s,Args... args)
{
  return format(s.c_str(), args...);
}


// base
std::string format(const char *s)
{
    printf("** %s\n",s);
    return "";
}
// recursive, should parse each argument and print the formatted string
template<typename T, typename... Args>
std::string format(const char *s, T value, Args... args)
{
    printf("** %s\n",s);  // dummy
    return "";
}

int main()
{
   std::string s = "foo";
   format(s);   
   printf("yay!\n");  // crashes before that
}

I could ditch const char * version altogether and go full std::string but I would like to avoid to build a string when a string literal is passed.

So how to keep the ability to pass either const char * directly or std::string as first argument ?


Solution

  • Your recursive version does not see any declarations to which it could delegate in its return statement.

    This is the uppermost declaration and definition you have in your code:

    template<typename... Args>
    std::string format(const std::string &s,Args... args)
    {
        return format(s.c_str(), args...);
    }
    

    It doesn't "see" any other candidates, thus it will always call itself recursively.

    You can fix it by introducing a declaration to your base case above it. Or moving said case there including its implementation:

    #include <stdio.h>
    #include <string>
    
    // base
    std::string format(const char *s)
    {
        printf("** %s\n",s);
        return "";
    }
    
    // recursive
    template<typename... Args>
    std::string format(const std::string &s,Args... args)
    {
        return format(s.c_str(), args...);
    }
    
    // recursive, should parse each argument and print the formatted string
    template<typename T, typename... Args>
    std::string format(const char *s, T value, Args... args)
    {
        printf("** %s\n",s);  // dummy
        return "";
    }
    
    int main()
    {
        std::string s = "foo";
        format(s);
        printf("yay!\n");  // crashes before that
    }
    

    Output:

    ** foo
    yay!