C++17: Keep only some members when tuple unpacking

C++TuplesC++17

C++ Problem Overview


Let's imagine you need to call the following method:

std::tuple<int, int, int> foo();

In C++17, you can call the function and unpack the tuple in a single line:

auto [a, b, c] = foo();

Now, how can I proceed to store only b and c and to discard a?

Currently, I'm only aware of two options:


1 - I can use a dummy variable when auto-unpacking

However, the dummy variable will be unused and it will issue a warning, so if I want to silent that warning the code will be quite unpleasant to see:

#pragma warning(push)
#pragma warning(disable:4101)
// ReSharper disable once CppDeclaratorNeverUsed
auto [_, b, c] = foo();
#pragma warning(pop)

2 - I can store the whole tuple and use std::get to retrieve the reference to the only variables I need. The code is less unpleasant but the syntax is also less straightforward.

Moreover, this code's size increases by one line for each new value that we want keep in the tuple.

auto tuple = foo();
int b = std::get<1>(tuple);
int c = std::get<2>(tuple);

Is there another and more straightforward method to unpack only some parameters in a tuple?

C++ Solutions


Solution 1 - C++

Another alternative is to use an std::tie:

int b, c;
std::tie(std::ignore, b, c) = foo();

Edit

As mentioned in the comments, there are some issues with this approach:

  • No type inference possible
  • The objects must be constructed before, so unless the default constructors are trivial, it's not a good alternative.

Solution 2 - C++

Unfortunately structured bindings do not explicitly support discarding members, and attributes such as [[maybe_unused]] cannot be applied to structured bindings (there's a proposal for that: P0609: "Attributes for Structured Bindings").

Here's a possible solution:

auto [a, b, c] = foo();
(void) a; // unused

Solution 3 - C++

You could write a helper function that only gives you back certain indices of a std::tuple:

template <size_t... Is, typename Tuple>
auto take_only(Tuple&& tuple) {
    using T = std::remove_reference_t<Tuple>;

    return std::tuple<std::tuple_element_t<Is, T>...>(
        std::get<Is>(std::forward<Tuple>(tuple))...);
}

auto [b, c] = take_only<1, 2>(foo());

Or drops the head or something:

template <size_t... Is, typename Tuple>
auto drop_head_impl(Tuple&& tuple, std::index_sequence<0, Is...> ) {
    return take_only<Is...>(std::forward<Tuple>(tuple));
}

template <typename Tuple>
auto drop_head(Tuple&& tuple) {
    return drop_head_impl(std::forward<Tuple>(tuple),
        std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>());
}

auto [b, c] = drop_head(foo());

But the above implementations almost certainly have some lifetime complexity issues that directly using structured bindings won't - since there isn't any lifetime extension here.

So just, do what Vittorio says:

auto [a, b, c] = foo();
(void)a;

Solution 4 - C++

MSVC has already fixed this in VS 15.7 Preview. The final 15.7 release should be available in the coming weeks. This means that the current logic supported by the latest releases of all major compilers is as follows:

  • If at least one of the structured bindings in a structured binding declaration is used, no "Unused variable" warning will be issued for other bindings in the same declaration.

  • If none of the bindings in a structured binding declaration are used, it is possible to silence the warning by using the [[maybe_unused]] attribute:

    [[maybe_unused]] auto [a, b, c] = foo();

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAntoine C.View Question on Stackoverflow
Solution 1 - C++MansuroView Answer on Stackoverflow
Solution 2 - C++Vittorio RomeoView Answer on Stackoverflow
Solution 3 - C++BarryView Answer on Stackoverflow
Solution 4 - C++Igor AkhmetovView Answer on Stackoverflow