C++17:在元组解包时仅保留某些成员

85

假设您需要调用以下方法:

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

C++17中,您可以在单行中调用函数并拆包元组:

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

现在,我该如何只存储bc并丢弃a

目前,我只知道两种选择:


1-当自动解包时,可以使用一个虚拟变量。

然而,虚拟变量将未使用并会发出警告,因此如果我想要消除该警告,代码将会看起来非常不愉快:

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

2 - 我可以存储整个元组,并使用 std::get 检索我需要的唯一变量的引用。代码不那么难看,但语法也不太直观。

此外,每当我们想要在元组中保留新值时,此代码的大小就会增加一行。

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

有没有另一种更直接的方法来解包元组中的某些参数?

1
如果警告是您主要关注的问题,我想您可以尝试使用 [[maybe_unused]] 来消除它。 - Baum mit Augen
2
@BaummitAugen 嗯,Visual Studio 似乎并不在意,并且仍然在编译时发出警告。 - Antoine C.
2
原始提案第3.7节中明确讨论了这一点。同时,“消除警告”被明确提及为不足够的用例。 - Some programmer dude
1
如果您不关心默认初始化变量,可以使用std::tiestd::ignore - Holt
4
请注意,ReSharper C++支持将[[maybe_unused]]应用于结构化绑定声明,Clang和GCC也支持此功能。如果整个声明中的至少一个结构绑定已使用,则Clang和GCC还会抑制“未使用”的警告 - 我将在ReSharper C++中实现相同的逻辑(RSCPP-22313)。我认为MSVC应该支持这两种机制,可能值得向他们提出请求。 - Igor Akhmetov
显示剩余4条评论
4个回答

58

另一种选择是使用 std::tie

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

编辑

如评论中所述,这种方法存在一些问题:

  • 无法进行类型推断
  • 对象必须在之前构造完成,因此除非默认构造函数是微不足道的,否则这不是一个好的替代方案。

18
这种方法相当方便,但是无法使用类型推断,这仍然是一个重要的缺点。 - Antoine C.
5
另一个缺点是需要一些默认构造的对象实例,这可能并不总是可行或最优的。 - Vittorio Romeo
是的,在一些情况下它只是方便而已。 - Mansuro

49

2
顺便问一下,在结构化绑定声明中,标识符的正确术语是什么?例如,我应该如何称呼上面例子中的a?是“绑定”吗? - Vittorio Romeo
2
我认为没有特定的名称。标识符或“结构化绑定的名称”是标准所称呼的吗? - Barry
@VittorioRomeo 这是 *name of an lvalue*。 - Mário Feroldi

23
您可以编写一个辅助函数,它仅返回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());

或者垂下头等动作:
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());

但是,上述实现几乎肯定存在一些生命周期复杂性问题,直接使用结构化绑定将没有这些问题 - 因为这里没有任何生命周期扩展。

所以,就像Vittorio所说的那样

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

1
返回传入元组和所需元素的组合将很好地模拟生命周期扩展。 - Yakk - Adam Nevraumont

7

MSVC已经在VS 15.7预览版中修复了这个问题。最终的15.7版本应该会在未来几周内发布。这意味着,所有主要编译器的最新版本都支持以下逻辑:

  • 如果结构化绑定声明中至少有一个被使用,那么不会对同一声明中的其他绑定发出“未使用变量”的警告。
  • 如果结构化绑定声明中没有任何绑定被使用,则可以通过使用[[maybe_unused]]属性来消除警告:

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

3
请注意,实际上您可能永远不需要使用第二种结构。如果您没有使用任何绑定,为什么要在第一次声明中使用结构化绑定? - Igor Akhmetov
哦,我错过了那个。 "如果没有绑定" - Alexander
@igorakhmetov RAII类型已声明但从未使用 ;)(我不确定返回多个RAII类型的函数是否是一个好主意) - lakshayg
我不认为第一个要点是正确的。至少在这个上下文中:for ([[maybe_unused]] auto [hash, state] : uniqueStates)hash 明显是未使用的,但即使加上 [[maybe_unsed]],我在 GCC 和 MSVC 中仍然会收到未使用变量的警告。 - Addy
我采取另一种方法:禁用“未使用的变量”警告,因为编译器的核心工作之一就是为我优化这种情况。它为我做的越多,我就能完成更多的工作,带来的价值也就越大。 - jstine
1
@jstine 我认为这个警告不是出于优化方面的考虑。想象一下,当你重构代码时,有未使用的变量留下来的情况。在这种情况下,很可能你会错过某些东西,或者至少它们现在是令人困惑的副产品。 - Burak

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