MSVC下使用std::apply处理空tuple触发C4189警告的原因及解决问询
在MSVC中使用std::apply处理空tuple时,会触发未引用变量警告(C4189)。以下受cppreference启发的代码可复现该问题:
#include <iostream> #include <tuple> #include <utility> template <typename... Ts> std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple) { std::apply( [&os](Ts const&... tupleArgs) { os << '['; std::size_t n{0}; ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...); os << ']'; }, theTuple); return os; } int main() { // 序列化示例 std::tuple myTuple{25, "Hello", 9.31f, 'c'}; std::cout << myTuple << '\n'; std::cout << std::tuple<>() << '\n'; }
启用/permissive-选项时,MSVC会针对最后一行代码输出警告:
(10): warning C4189: 'n': local variable is initialized but not referenced
(22): note: see reference to function template instantiation 'std::ostream &operator <<(std::ostream &,const std::tuple<> &)' being compiled
而GCC和Clang编译这段代码时不会产生此警告。由于使用了Werror/we选项,希望在不使用pragma的前提下消除该警告,现提出两个问题:
- MSVC为何会出现这种行为?
- 如何消除该警告(例如在lambda中添加
if constexpr (sizeof...(Ts)>0)?)
1. MSVC产生该警告的原因
当实例化空tuple对应的operator<<版本时,lambda中的折叠表达式((os << tupleArgs << ...), ...)会因模板参数包Ts为空而展开为空语句,编译器不会生成任何相关代码。此时变量n虽被初始化为0,但后续没有任何代码对它进行读取或修改——哪怕原折叠表达式里有++n,空参数包下这段代码也不会被实例化。
MSVC在/permissive-模式下会进行更严格的代码检查,会识别出这种“初始化后完全未被使用”的变量并触发C4189警告。而GCC和Clang在这种场景下会对空参数包导致的未使用变量做特殊处理,认为这是模板编程中的合理情况,因此不会发出警告。
2. 消除警告的方法
可以通过if constexpr针对空参数包的情况做分支处理,避免在空tuple场景下定义未使用的变量n,具体修改如下:
template <typename... Ts> std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> const& theTuple) { std::apply( [&os](Ts const&... tupleArgs) { os << '['; if constexpr (sizeof...(Ts) > 0) { std::size_t n{0}; ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...); } os << ']'; }, theTuple); return os; }
修改后,处理空tuple时if constexpr条件为假,内部的变量n和折叠表达式都不会被实例化,从根源上避免了未引用变量的问题,也就不会触发C4189警告。
另外也可以用std::index_sequence实现计数器逻辑替代,但if constexpr的方式更直观,也更贴合原有代码结构。
内容的提问来源于stack exchange,提问作者Oersted

