13

An issue I keep facing is one where the compiler complains about an unused variable, even though the variable is used, but it's only used inside a parameter pack expansion that happens to be empty for a specific instantiation. For example:

template <std::size_t... I>
auto func1(std::index_sequence<I...>)
{
  auto var = get_tuple();
  return func2(std::get<I>(var)...);
}

auto a = func1(std::make_index_sequence<0>());

See live example (try changing the tuple at line 4, by adding an int inside <> to see the warning go away). I know I could add a (void)var; line to make the warning go away, but it feels dirty to me, especially when the function is actually just a single line. I also don't want to disable this warning globally, because it does provide insight sometimes.

A similar manifestation of this issue is when the variable is used in a lambda capture. In this case, gcc spits no warning, while clang complains (I think gcc never implemented a warning about unused lambda captures):

template <std::size_t... I>
auto func1(std::index_sequence<I...>)
{
  auto var = get_tuple();
  auto my_lambda = [var](){
    return func2(std::get<I>(var)...);
  };
  return my_lambda();
}

auto a = func1(std::make_index_sequence<0>());

clang example

dcmm88
  • 1,445
  • 1
  • 12
  • 30
  • 2
    Use clang and file a bug report to gcc :P – Rakete1111 Sep 28 '17 at 15:33
  • 2
    You can still `pragma push/pop` around the function to disable warning. but `static_cast(var);` seems better workaround. – Jarod42 Sep 28 '17 at 15:36
  • How about `return func2(std::get(get_tuple())...);`? – nwp Sep 28 '17 at 15:37
  • @Jarod42 It might not be obvious this is to silence a warning instead of a botched up job of forcing a read from `var` or some other weirdness – Passer By Sep 28 '17 at 15:38
  • 2
    @nwp Isn't that going to execute `get_tuple` multiple times, which may or may not always return the same thing? Also maybe be a performance problem if it does a lot of stuff. – Rakete1111 Sep 28 '17 at 15:40
  • @Rakete1111 clang maybe handles the regular unused variable better, but complains when this variable is in a lambda capture. – dcmm88 Sep 28 '17 at 16:08
  • @nwp this is just a minimal example to show the warning, not something I intend to use in production (and also what Rakete1111 replied as well). – dcmm88 Sep 28 '17 at 16:10

5 Answers5

7

If you can use C++17, the [[maybe_unused]] attribute is the clearest solution IMO:

[[maybe_unused]]
auto tuple = get_tuple();
Rakete1111
  • 47,013
  • 16
  • 123
  • 162
5

var is indeed not use with empty pack. Is it intended ? compiler can only guess.

Whereas clang consider than empty pack is a usage, gcc chooses the contrary.

You can silent the warning in different ways as:

  • attribute [[maybe_unused]] (C++17)
  • casting to void (static_cast<void>(arg))
  • or similar (template <typename T> void unused_var(T&&){} and then unused_var(var)).
  • creating overloads:

    auto func1(std::index_sequence<>)
    {
      return func2();
    }
    
    template <std::size_t... I>
    auto func1(std::index_sequence<I...>)
    {
      auto var = get_tuple();
      return func2(std::get<I>(var)...);
    }
    

    or in C++17

    template <std::size_t... I>
    auto func1(std::index_sequence<I...>)
    {
        if constexpr (sizeof ...(I) == 0) {
            return func2();
        } else {
            auto var = get_tuple();
            return func2(std::get<I>(var)...);
        }
    }
    
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • 3
    A c-style cast to void is one of the few cases where C-style casts can provably not do bad behavior. In addition, `unused_var` could accidentally force ODR-existence of a variable if used without care. – Yakk - Adam Nevraumont Sep 28 '17 at 16:57
  • `[[maybe_unused]]` is not supported by GCC prior to 7 – Kokos Mar 18 '21 at 21:58
3

This seems to be a compiler bug in GCC. The easiest workaround is to mark var with [[gnu::unused]]:

template <std::size_t... I>
auto func1(std::index_sequence<I...>)
{
  auto var [[gnu::unused]] = get_tuple();
  return func2(std::get<I>(var)...);
}

If you are force to use compilers that don't recognize [[gnu::unused]], you can fake use the variable with a static_cast<void>:

template <std::size_t... I>
auto func1(std::index_sequence<I...>)
{
  auto var = get_tuple();
  static_cast<void>(var);
  return func2(std::get<I>(var)...);
}
Travis Gockel
  • 26,877
  • 14
  • 89
  • 116
2

(void)var; suppressed unused warnings in every compiler I have used:

template <std::size_t... I>
auto func1(std::index_sequence<I...>)
{
  auto var = get_tuple();
  (void)var;
  return func2(std::get<I>(var)...);
}
auto a = func1(std::make_index_sequence<0>());

(void)variable; has zero run time effects.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
-1

Maybe there are other problems but... according the code you linked in compiler explorer, your var is a std::tuple<>; that is a std::tuple with zero components.

If I'm not wrong, std::get<Num>(std::tuple<Ts..>) is defined only when Num is in [0,sizeof...(Ts)); in this case in [0, 0), that is an empty interval.

I suppose that your code (when var is defined as std::tuple<>) is ill formed. So I suppose that the warning is correct (because there isn't cases when var is used) but doesn't warn about the real problem.

It's different when var is defined as std::tuple<int>: var is correctly used when all I are equal to zero, so var is (potentially) used and, as you observed, the warning disappears.

max66
  • 65,235
  • 10
  • 71
  • 111
  • 2
    But if `var` is empty, the code expands to nothing, no? – Rakete1111 Sep 28 '17 at 15:46
  • @Rakete1111 - I suppose you're right but (if I'm not wrong) `var` is never unused; when `sizeof...(I)` is zero and when is grater. So the warning message. When the OP transform `var` in a `std::tuple`, the warning disappear because (as ever: if I'm not wrong) there are cases when `var` is correctly used: when (and only when) all `I` are zero. – max66 Sep 28 '17 at 15:53
  • Agreed, the code uses `var`, so the warning is wrong. Not sure I understand your last point. – Rakete1111 Sep 28 '17 at 16:01
  • @Rakete1111 - my last point is that if `var` is a `std::tuple<>` (warning case), `var` is **never** used (also when `sizeof...(I)` is greater than zero); when `var` is a `std::tuple` (no warning case), `var` is (correctly!) used when all `I` are equal to zero. So (if I'm not wrong, as usual) the compiler is right (in both cases) but, in the first case, the real problem is another (a call to `std::get(var)` when `var` is a `std::tuple<>` is wrong in any case). – max66 Sep 28 '17 at 16:06
  • @max66 Yes, the reason it complains about unused variables is that the parameter pack expansion is empty. So `var` isn't really used after expansion. But my whole point is that the warning is useless for me. Note that clang doesn't complain about the exact same code (though it complains in another case). Also, nothing in my code is UB. There is nothing wrong with `std::get(var)` when `var` is a `std::tuple<>`, because it's part of parameter pack expansion expression that evaluates to nothing. So there is never a call such as `std::get?>(var)` being made. – dcmm88 Sep 28 '17 at 16:22
  • @dcmm88 - I see... well, I'm not a standard layer but... according it, a call to `std::get(var)` (whan `var` is a `std::tuple<>`) is "ill formed"; but you're righr: when `sizeof...(I) == 0`, the ill formed call isn't expanded; sorry but I don't if this is enough to make you're code not "ill formed" (maybe I'll write another question, about it). Anyway, I think the compuler is correct emitting a warning and, in cases like this one, I suppose that a `(void)var` is an acceptable solution. – max66 Sep 28 '17 at 16:30
  • @dcmm88 - en passant, I think you should make more clear, in your question, that `var` is a `std::tuple<>` (when you get the warning). – max66 Sep 28 '17 at 16:31
  • @max66 but it doesn't have to be. The example would still be valid even if `get_tuple` returns an `std::tuple`. – dcmm88 Sep 28 '17 at 16:35
  • @dcmm88 - the case `std::tuple` is different (from the point of view of the compiler, when compile `func1()`) because there are possible calls to `func1()` (by example: `func1(std::make_index_sequence<1>());`) that cause the use of `var`; with `std::tuple<>`, with every call to `func1()` `var` is unused. (As usual: if I'm not wrong). – max66 Sep 28 '17 at 16:39
  • @max66 I was just replying to your suggestion to make the question clearer by explaining that `get_tuple` returns an `std::tuple<>`. What I said was that even if `get_tuple`'s return type was `std::tuple` (while my example stays exactly the same with the call to `make_index_sequence<0>`, the behavior I was referring to would still exist (the compiler would still complain about an unused variable). So, in summary, the kind of tuple `get_tuple` returns is irrelevant to my question/example. – dcmm88 Sep 28 '17 at 17:00
  • @dcmm88 - I hope that something more expert than me can see your improved question. What I trying to say is that the compiler, for a template function, doesn't evaluate only the call with `make_index_sequence<0>` but consider if there is a call when `var` is used; in the case `std::tuple<>`, there isn't; with `std::tuple`, a using-`var` call is possible (`make_index_sequence<1>`). – max66 Sep 28 '17 at 17:09