C++语言模板问题

6
以下是一个小的测试用例,展示了我在尝试使用C++模板解决问题时遇到的问题:
template<typename T>
void
unused(T const &) {
  /* Do nothing. */
}

int main() {
  volatile bool x = false;
  unused(!x); // type of "!x" is bool
}

如下所述,g++ v3.4.6编译器报错:
test.cc: In constructor `test::test()':
test.cc:11: error: invalid initialization of reference of type 'const volatile bool&' from expression of type 'volatile bool'
test.cc:3: error: in passing argument 1 of `void unused(const T&) [with T = volatile bool]'

这里的目标是在优化后的代码中消除未使用的变量警告。我有一个宏来进行断言检查,但在优化后的代码中,该断言会消失,但我希望断言表达式中的任何变量仍然保持引用,以便我不仅在优化后的代码中获得未使用变量的警告。在unused()模板函数的定义中,我使用引用,以便不会无意中运行复制构造函数代码,从而使对unused的调用可以被编译器完全省略。
对于那些感兴趣的人,断言宏看起来像这样:
#ifdef NDEBUG
#  define Assert(expression) unused(expression)
#else // not NDEBUG
#  define Assert(expression)      \
{         \
  bool test = (expression);      \
     \
  if (!test) {        \
    if (StopHere(__LINE__, __FILE__, __PRETTY_FUNCTION__,  \
                    #expression, false)) {    \
      throw Exit(-1); /* So that destructors are run. */  \
    }         \
  }         \
}
#endif // else not NDEBUG

对于上述测试用例,我可以通过添加另一个类似的未使用的函数来消除错误,如下所示:
template<typename T>
void
unused(T const) {
  /* Do nothing. */
}

然而,当参数可以像这样被引用时,调用unused()的其他情况会因歧义而失败:
file.h:176: error: call of overloaded `unused(bool)' is ambiguous
myAssert.h:27: note: candidates are: void unused(T) [with T = bool]
myAssert.h:34: note:                 void unused(const T&) [with T = bool]

我的问题是,如何更改unused()或重载它以满足以下要求:
  1. 编译器可以将对unused()的调用优化为无操作。
  2. 它使得传递给unused()的表达式中出现的任何变量都被视为已使用,因此不会导致警告,即这些变量被定义但未使用。
  3. unused()的参数可能可以被引用,也可能不能被引用。
  4. unused()的参数可能是具有昂贵的复制构造函数的对象,在调用unused()时不应该调用该函数。
谢谢。
-William

2
拒绝第一个代码片段看起来像是GCC错误:!some_volatile_bool的类型是bool而不是volatile bool - 因此,T应该被推导为bool而不是volatile bool。请注意,非类类型的右值永远不会被声明为const/volatile。 - Johannes Schaub - litb
1
已报告:http://gcc.gnu.org/bugzilla/show_bug.cgi?id=42655 - Johannes Schaub - litb
对我来说,这看起来像是Herb Sutter在他的博客中提到的重复内容,http://herbsutter.wordpress.com/2009/10/18/mailbag-shutting-up-compiler-warnings/ 顺便问一下,你在用哪个编译器? - Narendra N
可能存在一个错误,但如果他没有调用 unused(readWriteActivated),那听起来也像是他的意图。 - Potatoswatter
g++ v3.4.6,但这个错误在以下版本中也很明显:4.0.0 4.1.0 4.2.0 4.3.0 4.4.0 4.5.0 - WilliamKF
10个回答

3
一种常见(也更简单)的方法是将结果转换为void
(void) x;

其中 x 是某个未被引用的值。


这不是正确的。我们仍然收到警告。这段代码只有部分有效。它不能消除所有编译器上的警告。 - Jagannath
1
@Jagannath:我认为在测试Beta编译器时并不是公平的游戏,因为你已经说过VC2010是罪魁祸首。试试其他几乎所有实际发布的编译器。 - Evan Teran
@Evan:同意。会尝试在其他编译器上测试。 - Jagannath

2

Charles Nicholson建议这样做来标记未使用的变量,原因在这篇文章中解释:

#define UNSUSED(a) ((void)sizeof(a))

简单来说,sizeof不会评估表达式,但当它在这个上下文中被看到时,编译器仍然将其视为“已使用”。
我认为这符合您的所有4个标准,特别是因为sizeof()可以采用任何有效的表达式,并且因为该表达式不会被评估(因此不会生成任何代码)。

1
我见过的最好的解决方案是这样的:
#define UNUSED(x) ((void)x)

它是可移植的,并且可以抑制警告成功。

编辑

既然您已经说明这更像是一个断言,那么您应该做类似于这样的事情:

#if defined ASSERT_ENABLED
#define TEST(test) (!(test)) ? assert_failed(# test, __FILE__, __LINE__) : (void)0    
#else
#define TEST(ignore) ((void)0)
#endif

如果未定义ASSERT_ENABLED,则不会生成任何代码,并且不会产生有关未使用变量的警告。这基本上是libc中assert宏的工作方式。

我想问题在于变量仅在断言中使用,这是实现所需功能的不良方式。为什么不将其标记为未使用并单独使用断言宏,以便清楚地表明该变量实际上没有用于任何内容,但您仍然可以获得调试构建断言。只需逐个解决问题即可。


@Evan,你编译了这个吗?我在VC2010上收到了警告。 - Jagannath
@Jagannath: 我从来没有使用过VC2010,但是将类型转换为void应该是正确的解决方案,并且在我之前使用过的几乎所有编译器上都有效。 - Evan Teran
在#else子句中,那不应该是((void)ignore)吗? - WilliamKF
如果你只想要一个普通的assert风格的测试宏,那么不行。但是如果你愿意,你可以按照你所建议的方式去做,这样也可以正常工作。就像我刚刚编辑的那样,我会将未使用和断言问题完全分开处理,从而避免整个问题。 - Evan Teran

1

改变你对未使用的定义:

inline void unused(bool) {}

既然您已经需要一个需要转换为bool的表达式,那么这将执行该转换而不进行其他操作。内联允许编译器进行优化,包括在表达式没有副作用的情况下(但是您需要进行测试以了解在复杂情况下会发生什么)。

此外,这解决了大多数assert宏的常见问题:如果表达式具有副作用,则始终会评估这些副作用。(根据使用情况,这可能非常好或非常糟糕。)


这通常不起作用,例如,如果断言的参数无法转换为bool,则会出现问题,因此需要使用模板。 - WilliamKF
你已经在当前的宏中使用了 bool test = (expression);,因此已经需要这个属性 - Roger Pate

1

正如 Johannes 在评论中所说,你遇到了编译器的一个 bug。你可以通过显式转换为 bool 来解决它:

unused( bool( !readWriteActivated) ); // add bool() to any (!volatile_bool_var)

旧答案(但仍然不错的想法)

如果我记得const-volatile限定规则,你所需要的就是更多地限定虚拟变量。基本上,你只需要在声明类型中回显错误消息 :vP。

template<typename T>
void
unused(T const volatile &) { // only change is to add "volatile"
  /* Do nothing. */
}

另外,很好你将 const 放在类型后面,这是正确的写法。


从语言定义上来看,const 可以放在任何一个地方,都是正确的,但这并不清楚。添加 volatile 也无法解决问题:test.cc:11: error: invalid initialization of reference of type 'const volatile bool&' from expression of type 'volatile bool' test.cc:3: error: in passing argument 1 of `void unused(const volatile T&) [with T = bool]' - WilliamKF
1
const 应该放在哪里是个人观点问题。代码实际上无论有没有任何限定符都没问题;错误提示表明编译器存在 bug。 - Potatoswatter
在我的编译器版本 v3.4.6 中,将 bool 类型的转换添加到 unused() 函数的参数周围并不能解决这个 bug。 - WilliamKF
哇,那真的很糟糕,而且相当令人惊讶。虽然GCC 4已经发布一段时间了,也许是时候升级了?我想下一个建议是将其转换为“int”,因为在bugzilla讨论中他们说这个问题只影响“bool”。 - Potatoswatter

1
编译器警告与使用或未使用无关。您正在将一个易失变量 - readWriteActivated - 传递给一个不接受易失引用的函数。尝试使用const_cast。

0
如果你想要抑制未使用变量的警告,为什么要这样调用:
unused(!readWriteActivated); ?为什么不能直接这样调用:
unused(readWriteActivated); 并将代码改为:
template<typename T>
void UnUsed(const T& )
{

}

更多参考请查看Herb Sutter的博客文章这里

编辑:在函数中删除了参数名称。这同样适用于未使用的(!readWriteActivated)。


1
不要为参数命名,否则您将收到警告,“ignore”未使用 :-P。 - Evan Teran
因为真正的代码有一个!readWriteActivated的断言,在优化版本中,这个断言消失了,但是当断言是变量的唯一使用时,我不想要未使用变量的警告。 - WilliamKF

0

这是一个错误:

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=42655

在未使用()级别上没有解决方法。相反,每个出现可以通过在调用unused()之前引入临时变量来解决:

template<typename T>
void
unused(T const &) {
  /* Do nothing. */
}

int main() {
  volatile bool x = false;
  bool avoidGCC42655 = !x; // type of "!x" is bool
  unused(avoidGCC42655);
}

0
为什么不完全跳过模板,直接使用省略号呢?
inline void unused (...) { /* do nothing */ }

然后在它前面加上一个 & - eduffy
可变参数函数必须至少有一个非可变参数。 - Roger Pate

-1
为什么在表达式的值无关紧要时,你要传递 !readWriteActivated 而不是 readWriteActivated

我不确定为什么这个问题被投票否决了。实际上,这是一个恰当的问题。 - Jagannath
测试用例被简化了。在实际代码中,否定是必需的,因为它是一个断言,去掉否定会得到错误的断言。 - WilliamKF
@WilliamKF:如果这是一个断言,那么你应该发布演示所需用法的代码(你是想要生成类似于assert的东西吗?) - Evan Teran
我试图将测试用例简化为一些小的东西。这个断言看起来像:#ifdef NDEBUG

define Assert(expression) unused(expression)

#else // not NDEBUG

define Assert(expression) \

{
bool test = (expression);

if (!test) {
if (StopHere(LINE, FILE, PRETTY_FUNCTION,
#expression, false)) {
throw Exit(-1); /* So that destructors are run. */
}
}
} #endif // else not NDEBUG
- WilliamKF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接