Search code examples
c++visual-c++c++17compiler-warningsstdapply

unexplained warning when using std::apply on empty tuple with msvc


I have a warning about unreferenced variable when using std::apply on an empty tuple. This snippet, inspired from std::apply cppreference shows the issue:

#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() {
    // serialization example
    std::tuple myTuple{25, "Hello", 9.31f, 'c'};
    std::cout << myTuple << '\n';
    std::cout << std::tuple<>() << '\n';
}

Live
MSVC, with /permissive-, outputs the following warning with the very last line:

(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 and clangs do not issue anything.

As I'm using also Werror//we I'd like to get rid of this warning (without using pragma, if possible).

  1. Why does msvc behaves like that?
  2. How can I get rid of this warning (adding a if constexpr (sizeof...(Ts)>0) inside the lambda?)?

Solution

  • Proposing an answer from the comments.

    From fold expression cppreference:

    Explanation ...
    When a unary fold is used with a pack expansion of length zero, only the following operators are allowed:

    1. Logical AND (&&). The value for the empty pack is true
    2. Logical OR (||). The value for the empty pack is false
    3. The comma operator (,). The value for the empty pack is void()

    emphasis mine. Thus in ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);, if the tuple is empty, the expression becomes void();, resulting to n not being used, thus msvc is right to issue a warning.

    Then, this warning can be silenced by a mere [[maybe_unused]] std::size_t n{0};.

    Arguably we could also write:

    #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 << '[';
                if constexpr (sizeof...(Ts)) {
                    std::size_t n{0};
                    ((os << tupleArgs << (++n != sizeof...(Ts) ? ", " : "")), ...);
                }
                os << ']';
            },
            theTuple);
        return os;
    }
    
    int main() {
        // serialization example
        std::tuple myTuple{25, "Hello", 9.31f, 'c'};
        std::cout << myTuple << '\n';
        std::cout << std::tuple<>() << '\n';
    }
    

    Live

    The if constexpr is more verbose but also more explicit with respect to when n is actually unused.
    Strangely enough, I couldn't find a way to make gcc and clang issue a similar warning.