C ++ 17:打开元组时仅保留一些成员


69

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

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]]
鲍姆·米特·奥根

2
@BaummitAugen很好,Visual Studio似乎不在乎,无论如何在编译时发出警告。
Antoine C.

2
原始建议在3.7节中明确讨论了这一点。明确提到“静默警告”用例还不够。
一些程序员花花公子

1
如果您不关心默认初始化变量,则可以std::tie与结合使用std::ignore
Holt '18年

2
@Lovy请注意,ReSharper C ++确实支持[[maybe_unused]]将其应用于结构化绑定声明,clang和GCC也是如此。如果在整个声明中至少使用了一种结构化绑定,那么Clang和GCC还会抑制“未使用”警告-我将在ReSharper C ++(RSCPP-22313)中实现相同的逻辑。在我看来,MSVC应该支持这两种机制,可能值得向它们提出请求。
伊戈尔·阿赫梅托夫

Answers:


44

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

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

编辑

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

  • 不能进行类型推断
  • 必须先构造对象,因此,除非默认构造函数很简单,否则它不是一个好的选择。

17
这非常方便,但是无法在此方法中使用类型推断,这仍然是一个重要的缺点。
Antoine C.

5
另一个缺点是,您需要一些默认构造的对象实例,这些实例可能并不总是可能的或最佳的。
罗密欧

是的,仅在少数情况下很方便
曼苏罗

40

不幸的是,结构化绑定不明确支持丢弃成员,并且诸如之类的属性[[maybe_unused]]无法应用于结构化绑定(有一个建议:P0609:“结构化绑定的属性”)。

这是一个可能的解决方案:

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

2
顺便说一句,结构化绑定声明中的标识符的正确术语是什么?例如a,在上面的示例中,我该怎么称呼?有“约束力”吗?
罗密欧

2
我认为没有特定的名称。标识符或“结构化绑定的名称”是标准所说的吗?
巴里(Barry)


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

6

MSVC已在VS 15.7预览版中修复了此问题。最终的15.7版本应在未来几周内可用。这意味着所有主要编译器的最新版本支持的当前逻辑如下:

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

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

3
@Alexander注意,在实践中,您可能永远不需要第二个构造。如果您不使用任何绑定,那么为什么要首先使用结构化绑定声明?
伊戈尔·阿赫梅托夫

哦,我错过了。“如果没有约束力”
亚历山大

@igorakhmetov RAII类型已声明,但从未使用过;)(我不确定返回多个RAII类型的函数是否是一个好主意)
lakshayg,

我不认为第一个项目符号是正确的。至少在这种情况下:for ([[maybe_unused]] auto [hash, state] : uniqueStates)hash是绝对未使用的,但是即使[[maybe_unsed]]我仍然在GCC和MSVC中都收到未使用的变量警告。
Addy

我采用另一种方法:我禁用unused variable警告,因为编译器的核心工作之一就是为我优化这种事情。它为我做的事越多,我做的越多,带给我的价值就越大。
jstine
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.