模板函数返回void

时间:2015-02-19 06:11:06

标签: c++ templates

我的日志记录类中有以下功能:

template<class T> 
inline T ErrLog(T ret, const char* Format, ...)
{
    va_list args; va_start( args, Format ); _vsnprintf(mypWorkBuffer, MaxLogLength, Format, args); va_end(args);
    // do some fancy logging with mypWorkBuffer
    return ret;
}

(mypWorkBuffer在别处定义)

这对我来说是一个非常方便的快捷方式,因为我可以记录错误并退出一行,这样可以通过错误处理来使代码更具可读性:

int f(x) {
   if (x<0) return ErrLog(-1, "f error, %d too small", x);
   ...
}

(而不是

int f(x) {
   if (x<0) {
      Log("f error, %d too small", x);
      return -1;
   }
   ...
}

我遇到的问题是f返回void。我想做

void f(x) {
   if (x<0) return ErrLog(void, "f error, %d too small", x);
   ...
}

但这不会编译。

我想到了专业化,即添加:

inline void ErrLog(const char* Format, ...)
{
    va_list args; va_start( args, Format ); _vsnprintf(mypWorkBuffer, MaxLogLength, Format, args); va_end(args);
    // do some fancy logging with mypWorkBuffer
    return;
}

这允许我做

return ErrLog("f error, %d too small", x);

但是我不确定返回char *的函数是否安全。例如,考虑:

char* f(x) {
   if (x<0) return ErrLog("error", "f error , %d too small", x);
   ...
}

我认为这个会匹配模板和专业化。

任何想法/更好的解决方案?

3 个答案:

答案 0 :(得分:5)

我认为使用逗号运算符更加惯用,更简单:

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);
}

逗号运算符的左手大小可以是void表达式。

这样就不需要使用模板化功能了。

编辑:如果你真的不喜欢使用逗号...

如果您真的想使用功能界面,则有三个选项。 给定一个泛型函数(我在这里使用std::forward,以便我们可以将它与引用一起使用等):

template <class T>
inline T &&ErrLog(T &&ret,const char *format,...) {
    /* logging ... */
    return std::forward<T>(ret);
}

你可以:

1)对void case使用单独的函数。

void ErrLogV(const char *format,...) {
    /* logging ... */
}

void foo1(int x) {
    if (x<0) return ErrLogV("foo1 error");
}

2)使用特殊的“标记”类型进行重载:

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)或者使用一个扔掉的参数并转换为void:

void foo3(int x) {
    // uses generic ErrLog():
    if (x<0) return (void)ErrLog(0,"foo3 error");
}

第二次修改:为什么没有ret参数的版本无法正常工作

您可以使用ret安全地定义版本;它并不含糊,但是当你想要它时它不一定会被使用。根本问题在于,在一个上下文中,您将需要一个行为,而在另一个上下文中,您将需要其他行为,但函数调用参数将具有完全相同的类型。

考虑以下示例:

#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");
}

这将编译!但只会调用foo的第一个版本。编译器无法将您的意图与参数类型区分开来。

为什么不含糊?

foo的两个版本都将成为重载解析的候选者,但第一个版本将是更好的匹配。您可以参考detailed description of the process(尤其是排名部分),但简而言之,当您有两个或更多const char *个参数时,const char *第一个版本foo的第二个参数const char * 1}}比第二个版本的省略号参数更具体。

如果你只有一个foo("this will use the void-version"); 参数,那么返回void的版本将胜过通用版本,因为非模板重载优先于模板重载,其他条件相同:

void

简而言之,使用重载将编译,但会给出令人惊讶且难以调试的行为,并且无法处理{{1}} - 返回版本需要多个参数的情况。

答案 1 :(得分:0)

对于void问题,请使用返回0的int专门化,然后忽略它:

void f(x) {
   if (x<0) {
      ErrLog(0, "f error, %d too small", x);
      return;
   }
   ...
}

对于返回char *的函数,如果返回的值是静态的,则可以使其安全(但是当您使用C ++时,您可以使用std::string):

char* f(x) {
   static char err[] = "error";
   if (x<0) return ErrLog(err, "f error , %d too small", x);
   ...
}

编辑:

对于void部分,我无法想象如何避免阻塞(独立于模板问题):

void g(int x);
void f(int x) {
    if (x<0) return g(x); // 2 errors here : g returns void and f returns void
}

如果函数g返回void,则无论此函数是什么,都不能将其用作return语句的值。无论如何,你不能在返回return something;的函数中使用void。我能想象的最好的(但不是你问的)是:

void f(x) {
   if (x<0) ErrLog(0, "f error, %d too small", x);
   else {
      ...
   }
}

为else部分创建另一个块...

答案 2 :(得分:0)

我建议您使用具有用户定义的Void类型的模板专精来处理您的Void案例。 对不起,这是C ++ 14,但您应该能够轻松转换它,只需相应地更改返回类型

template<class T>
auto ErrLog(T ret, const char* Format, ...)
{
    return ret;
}

struct Void{ };

template<>
auto ErrLog(Void ret, const char* Format, ...)
{
    return;
}

int main()
{
    ErrLog(Void{}, "f error, %d too small");
    ErrLog(-1, "f error, %d too small");
}

此外,我没有通过可变参数模板Args,这个解决方案更能向您展示这个想法。