I think it is more idiomatic and much, much simpler to just use the comma operator:
inline void Errlog(const char *format,...) { /* ... */ }
void f(int x) {
if (x<0) return Errlog("f error...");
}
double g(double x) {
if (x<0) return Errlog("x is negative..."),-1.0;
else return sqrt(x);
}
The left hand size of the comma operator can be a void expression.
This obviates the need to use a templated function at all.
Edit: if you really don't like using comma...
You have three options if you really want to use a function interface.
Given a generic function (I'm using std::forward here, so that we can use it with references and so on):
template <class T>
inline T &&ErrLog(T &&ret,const char *format,...) {
/* logging ... */
return std::forward<T>(ret);
}
You can either:
1) Use a separate function for the void case.
void ErrLogV(const char *format,...) {
/* logging ... */
}
void foo1(int x) {
if (x<0) return ErrLogV("foo1 error");
}
2) Overload with a special 'tag' type:
static struct errlog_void_t {} errlog_void;
inline void ErrLog(errlog_void_t,const char *format,...) {
/* logging ... */
}
void foo2(int x) {
if (x<0) return ErrLog(errlog_void,"foo2 error");
}
3) Or use a throw away argument and cast to void:
void foo3(int x) {
// uses generic ErrLog():
if (x<0) return (void)ErrLog(0,"foo3 error");
}
Second edit: why the version without the ret parameter can't work
You can safely define the version with ret; it's not ambiguous, but it won't necessarily be used when you want it to be. The fundamental problem is that in one context you will want one behaviour, and in another the other behaviour, but the function call parameters will have the exact same type.
Consider the following example:
#include <cstdarg>
#include <cstdio>
#include <utility>
using namespace std;
template <typename T>
T &&foo(T &&x,const char *format,...) {
puts("T-version");
va_list va;
va_start(va,format);
vprintf(format,va);
va_end(va);
puts("");
return forward<T>(x);
}
void foo(const char *format,...) {
puts("void-version");
va_list va;
va_start(va,format);
vprintf(format,va);
va_end(va);
puts("");
}
int main() {
foo("I want the void overload: %s, %s","some string","some other string");
foo("I want to return this string","I want the const char * overload: %s","some string");
}
This will compile! But only the first version of foo will be called. The compiler cannot distinguish your intent from the argument types.
Why is it not ambiguous?
Both versions of foo will be candidates for overload resolution, but the first one will be a better match. You can refer to a detailed description of the process (especially the section on ranking), but in short, when you have two or more const char * arguments, the const char * second parameter of the first version of foo is more specific than the ellipsis parameter of the second version.
If you have only one const char * argument, then the void-returning version will win over the generic one, because non-template overloads are prefered to template-overloads, other things being equal:
foo("this will use the void-version");
In short, using the overload will compile, but will give surprising and hard to debug behaviour, and can't handle the case where your void-returning version takes more than one argument.
reteven get passed throughErrLog? If it's not used or modified it doesn't really have any business being there. Doubly so when it's the only reason for this being a template.throw.