模板函数返回 void
template function returning void
我的日志中有以下功能class:
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 returns 无效。我想做
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);
...
}
我认为这个会匹配模板和专业化。
任何 thoughts/better 解决方案?
对于 void
问题,使用 int 特化 returning 0
然后忽略它:
void f(x) {
if (x<0) {
ErrLog(0, "f error, %d too small", x);
return;
}
...
}
对于 returning char *
函数,只要 returned 值是静态的,你就可以使其安全(但是当你使用 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
return 无效,则不能将其用作 return 语句的值,无论此函数是什么。无论如何,您不能在 returning void
函数中使用 return something;
。我能想象的最好的(但这不是你问的)是:
void f(x) {
if (x<0) ErrLog(0, "f error, %d too small", x);
else {
...
}
}
正在为 else 部分创建另一个块...
我认为使用逗号运算符更加地道,也更加简单:
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 情况使用单独的函数。
void ErrLogV(const char *format,...) {
/* logging ... */
}
void foo1(int x) {
if (x<0) return ErrLogV("foo1 error");
}
2) 使用特殊 'tag' 类型重载:
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 *
参数,那么返回 void 的版本将胜过通用版本,因为在其他条件相同的情况下,非模板重载优于模板重载:
foo("this will use the void-version");
简而言之,使用重载可以编译,但会产生令人惊讶且难以调试的行为,并且无法处理 void
-返回版本需要多个参数的情况。
我建议您使用具有用户定义的 Void 类型的模板专业化来处理您的 Void 案例。
抱歉,这是 C++14,但您应该能够非常轻松地转换它,只需相应地更改 return 类型
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,这个解决方案更多是为了给大家展示这个思路。
我的日志中有以下功能class:
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 returns 无效。我想做
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);
...
}
我认为这个会匹配模板和专业化。
任何 thoughts/better 解决方案?
对于 void
问题,使用 int 特化 returning 0
然后忽略它:
void f(x) {
if (x<0) {
ErrLog(0, "f error, %d too small", x);
return;
}
...
}
对于 returning char *
函数,只要 returned 值是静态的,你就可以使其安全(但是当你使用 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
return 无效,则不能将其用作 return 语句的值,无论此函数是什么。无论如何,您不能在 returning void
函数中使用 return something;
。我能想象的最好的(但这不是你问的)是:
void f(x) {
if (x<0) ErrLog(0, "f error, %d too small", x);
else {
...
}
}
正在为 else 部分创建另一个块...
我认为使用逗号运算符更加地道,也更加简单:
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 情况使用单独的函数。
void ErrLogV(const char *format,...) {
/* logging ... */
}
void foo1(int x) {
if (x<0) return ErrLogV("foo1 error");
}
2) 使用特殊 'tag' 类型重载:
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 *
参数,那么返回 void 的版本将胜过通用版本,因为在其他条件相同的情况下,非模板重载优于模板重载:
foo("this will use the void-version");
简而言之,使用重载可以编译,但会产生令人惊讶且难以调试的行为,并且无法处理 void
-返回版本需要多个参数的情况。
我建议您使用具有用户定义的 Void 类型的模板专业化来处理您的 Void 案例。 抱歉,这是 C++14,但您应该能够非常轻松地转换它,只需相应地更改 return 类型
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,这个解决方案更多是为了给大家展示这个思路。