开发者

Function overloading where parameters only differ by ellipses

开发者 https://www.devze.com 2023-01-14 23:55 出处:网络
I\'ve got this logging system for which I\'m looking to shortcut some of the string manipulation. The logging system is used via functional macros which then forward to a single function call.E.g. #d

I've got this logging system for which I'm looking to shortcut some of the string manipulation.

The logging system is used via functional macros which then forward to a single function call. E.g. #define Warning(...) LogMessage(eWarning, __VA_ARGS__);.

LogMessage then does a snprintf into a new buffer and then presents that message to whatever log targets happen to be installed; printf, OutputDebugString, etc.

Unfortunately, I've run into a problem where the buffer that we have isn't big enough, so the output gets truncated. I also r开发者_JAVA技巧ealized that this method will fail if the output message has percent symbols in it, as snprintf will try to process the va_args. Finally, as the majority of our log messages do not use the va_args, it seems silly to copy the string just to present it to the loggers.

So- given my function prototype, should I be able to overload based on the presence of the ellipses? In other words, should I be able to assume that I can do something like:

LogMessage(LogLevel, const char* message, ...);
LogMessage(LogLevel, const char* message);

My google attempts haven't yielded anything particularly useful (just showing me that ellipses will match if nothing else does, varying from my requirements that nothing matches), and my initial stab at an implementation just gave me an ambiguous function call error.

With the error, I should just accept that I can't do this, but I'm wondering if it's just the compiler I'm using or if maybe I'm doing it wrong. I can achieve a similar effect with

// edited version of what I really have to remove our local APIs,
// please excuse minor errors
const char* message = NULL;
char buffer[512];

va_list args;
va_start(args, format);

if(strcmp(format, "%s") == 0) {
    message = va_arg(args, const char*);
}
else if (strchr(format, '%') == NULL) {
    message = format;
}
else {
    vsnprintf(buffer, 512, format, args);
    message = buffer;
}

va_end(args);

...but this seems wasteful in the typical case which can be known simply by the number of parameters being passed. E.g. if ellipses don't match anything, select the other function? If this doesn't work, is there another method I can try that doesn't require the user to decide with the macro name which function will be called? Honestly it's not even as much about the "waste" once I realized that if someone haphazardly said Error("Buffer not 100% full"); in their log message and got "Buffer not 1007.732873e10ull" as a result.

Edit: While my example has been answered by a "don't do that," can the question itself be answered?


I was inspired by the original answer to this question, but have come up with a slight improvement.

static void LogMessage(LogLevel level, const char* message);

template <typename T>
static void LogMessage(LogLevel level, const char* format, T t, ...)
{
    LogMessageVA(level, format, (va_list)&t);
}

static void LogMessageVA(LogLevel level, const char* format, va_list argptr);

This works without having to 'assume' that the second argument is const char*.


I also realized that this method will fail if the output message has percent symbols in it, as snprintf will try to process the va_args.

Then caller beware. If your function is documented to take printf-style format strings then it is the caller's responsibility to escape any percent signs. It is not really your job to attempt to handle invalid format strings.

Honestly it's not even as much about the "waste" once I realized that if someone haphazardly said Error("Buffer not 100% full"); in their log message and got "Buffer not 1007.732873e10ull" as a result.

I think you're better off going along with the C++ ethos. In Java methods commonly check for valid arguments and throw exceptions when passed invalid values. In C++ you simply let callers shoot themselves in the foot. It's better to have them write 100%% than to jump through hoops to protect them from learning how to call your function properly.


In C++11 you can use variadic templates with an explicit specialization for the single-argument case:

void bar(int a, ...) {
  // va_list stuff
}

template <typename... T>
void foo(int a, T... args) { // (1)
  bar(a, args...); // or do all the vararg stuff here directly
}

template <>
void foo(int a) {            // (2)
  printf("single\n");
}

Then:

//foo();    // compile error, as expected
foo(1);     // uses (2)
foo(2,1);   // uses (1)
foo(3,1,"asdf"); // uses (1)
...


Ok I think I came up with a solution for the question.

The fact is that you can't overload based only on whether there are parameters for the ellipses or not. I.e. you can't have functions which have signatures that vary only on the presence of the ellipses.

However, it is possible to do something like what I was asking if I drop the const char* parameter from the ellipses prototype. I.e.

LogMessage(LogLevel, ...);
LogMessage(LogLevel, const char* message);

is unambiguous, but now you battle with the fact that you have to assume that the first parameter is a const char*, but it may well not be. Taking John Kugelman's advice, maybe that's fine; you document the parameters that are allowed and user beware. The non-ellipses function will be called if there is only a const char*, and the ellipses function will be called if there is any thing else including the documented const char* followed by some number of parameters.

Unfortunately it seems that this is the extent of a possible solution that allows you to pass the va_args on to child functions, in my example case to vsnprintf.

It's probably bad form to accept my own answer, even though it's the one that answers the presented question.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号