0

Consider the following code sample:

void my_print(bool is_red, const char* format, ...){
    va_list args;
    va_start(args, format);
    if(is_red) 
        print_red(format, args);
    else
        print_normal(format, args);
    va_end(args);
}

void my_print(const char* format, ...){
    va_list args;
    va_start(args, format);
    print_normal(format, args);
    va_end(args);
}


int main() {
    my_print((const char*)"Hello %s\n", "World");
    return 42;
}

This code is ambiguous to the compiler and it yields the error:

more than one instance of overloaded function "my_print" matches the argument list: function "my_print(bool is_red, const char *format, ...)" (declared at line 12) function "my_print(const char *format, ...)" (declared at line 22) argument types are: (const char *, const char [6])

From what I understand the parameters I passed can interpreted as either (const char*, ...) where the '...' is just 'const char*' or (bool, const char*, ...) where the '...' is empty.

I would expect the compiler to assume I want to call the one which does not require an implicit cast from 'bool' to 'const char*' (which I do), and hence call the second instance.

How can I clarify this ambiguity to the compiler without changing the syntax of the function call?

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
yosef
  • 124
  • 5
  • 1
    Why have you used the tag [tag:C], the language that does not have function overloads? – 273K Oct 23 '22 at 07:17
  • 3
    Using va_lists is legacy of "C", don't use it in C++. Use variadic template functions like [here](https://stackoverflow.com/questions/27375089/what-is-the-easiest-way-to-print-a-variadic-parameter-pack-using-stdostream) they are typesafe. So I would recommend changing the syntax of the function call. If you can't do that make a typesafe wrapper using variadic templates around it, but try to stay away from va_args as far as you can in the rest of your code. – Pepijn Kramer Oct 23 '22 at 07:31
  • https://en.cppreference.com/w/cpp/language/explicit – Jesper Juhl Oct 23 '22 at 08:14
  • Do not tag C for C++ questions. – Eric Postpischil Oct 23 '22 at 11:04
  • I have not been able to find the 'C++ way' to get the functionality of the printf format string, including templates. I would greatly appreciate any suggestions. – yosef Oct 23 '22 at 11:20

1 Answers1

1

You could rename both overloads of my_print to functions named differently and introduce a template names my_print instead, which allows you to add a check, if the type of the first parameter is char const* in an if constexpr to resolve the ambiguity.

void print_red(char const* format, va_list lst)
{
    printf("red    :");
    vprintf(format, lst);
}

void print_normal(char const* format, va_list lst)
{
    printf("normal :");
    vprintf(format, lst);
}

void my_print_with_color_impl(bool is_red, const char* format, ...) {
    va_list args;
    va_start(args, format);
    if (is_red)
        print_red(format, args);
    else
        print_normal(format, args);
    va_end(args);
}

void my_print_impl(const char* format, ...) {
    va_list args;
    va_start(args, format);
    print_normal(format, args);
    va_end(args);
}

template<class T, class ... Args>
void my_print(T first, Args ... args)
{
    if constexpr (std::is_same_v<std::decay_t<T>, char const*>)
    {
        my_print_impl(first, args...);
    }
    else
    {
        my_print_with_color_impl(first, args...);
    }
}

int main() {
    my_print("Hello %s\n", "World");
    return 42;
}

Alternatively pre C++17 you could create a template class for printing and partially specialize it based on the first parameter:

template<class T, class...Args>
struct Printer
{
    void operator()(bool is_red, char const* format, ...) const
    {
        va_list args;
        va_start(args, format);
        if (is_red)
            print_red(format, args);
        else
            print_normal(format, args);
        va_end(args);
    }
};

template<class...Args>
struct Printer<char const*, Args...>
{
    void operator()(char const* format, ...) const
    {
        va_list args;
        va_start(args, format);
        print_normal(format, args);
        va_end(args);
    }
};

template<class T, class ... Args>
void my_print(T first, Args ... args)
{
    (Printer<typename std::decay<T>::type, Args...> {})(first, args...);
}

int main() {
    my_print("Hello %s\n", "World");
    my_print(1, "Hello colorful %s\n", "World");
    return 42;
}
fabian
  • 80,457
  • 12
  • 86
  • 114