使用 `{}` 构造 std::tuple 时,Clang-Tidy 对于 std::move 是否正确?

问题描述 投票:0回答:1

我正在用 C++ 实现一个函数,特别是一个可调用的类对象,旨在返回一个包含对象容器和结果的

std::tuple
。经过一些调整(见下文)我有了成员函数的定义:

template <typename UserGroups>
auto operator()(const UserGroups& userGroups) const noexcept
{
    using result_t = std::tuple<std::unordered_set<QnUuid>, Result>;
    State state{.inheritingGroups = {m_rootId}};
    
    for (const auto& group : userGroups)
    {
        if (Result res = dfs(state, group); !res)
            return result_t({}, std::move(res));
    }
    return result_t(std::move(state.inheritingGroups), {});
}

... 其中

State
是:

struct State
{
    std::unordered_set<QnUuid> inheritingGroups{};
    std::unordered_set<QnUuid> visited{};
    std::unordered_set<QnUuid> visiting{};
};

Clang-Tidy 就这两个

return
语句向我发出警告:

Clang-Tidy:将 std::move() 的结果作为 const 引用参数传递;实际上不会发生任何举动

为了解决这个问题,我在第二个

Result()
语句中明确传递了
return

return result_t(std::move(state.inheritingGroups), Result());

我怀疑这可能是由于当

std::tuple
用作参数之一时选择了
{}
的非模板化构造函数。理想情况下,我的目标是简化的返回语法,例如:

// auto operator()(...) const -> std::tuple<std::unordered_set<QnUuid>, Result>

// If error occurs
if (Result res = dfs(state, group); !res)
    return {{}, std::move(res)}; // std::move is required since NRVO will not work

// On success
return {std::move(state.inheritingGroups), {}};

但是,使用

{...}
初始化元组会因构造函数调用不明确而导致编译问题。

为了解决这个问题,我引入了一个 typedef 别名,但它导致了一个更冗长且在我看来不太干净的版本:

using groups = std::unordered_set<QnUuid>;
using result_t = std::tuple<groups, Result>;

// If error occurs
if (Result res = dfs(state, group); !res)
    return result_t(groups(), std::move(res));
// On success
return result_t(std::move(state.inheritingGroups), Result());

我发现引入

groups
别名是一个次优的解决方案,因为它只能用来规避 Clang-Tidy 警告并引入不必要的复杂性。别名必须在直接作用域之外声明(
State
由 2 个函数使用),从而增加了额外的间接层,并可能导致未来的读者查找其定义,从而失去了它作为
unordered_set
的直接清晰度。

在这种情况下,我有两个主要问题:

  1. Clang-Tidy 所说的在这种情况下实际上不会发生移动是否准确?
  2. 当使用
    std::tuple
    作为参数时,我对
    {}
    选择非模板构造函数的怀疑是否正确?

我正在寻找见解或替代解决方案,以保持代码的清晰度和简单性,同时正确解决 Clang-Tidy 警告。任何建议或解释都非常感谢。

c++ c++17 move-semantics clang-tidy stdtuple
1个回答
0
投票

是的,clang-tidy 是正确的。

std::tuple
有很多构造函数,并且因为您使用的是
{}
,所以无法调用转发构造函数(这是一个模板)。 只能调用采用
const Types&...
的构造函数,因为不需要从
{}
中扣除模板参数。 这是 cppreference 上的构造函数 (2)。

一般来说,无法从

{}
推导出任何类型,这就是转发构造函数不可行的原因。请参阅为什么自动和模板类型推导对于花括号初始化器不同?

您可以按如下方式重现此问题:

#include <tuple>

struct S {
    S(const S&);
    S(S&&);
};

std::tuple<S, int> foo(S s) {
    return std::tuple<S, int>(std::move(s), {});
}

std::tuple<S, int> bar(S s) {
    return std::tuple<S, int>(std::move(s), int{});
}

编译为:

foo(S):
        // ...
        call    S::S(S const&) [complete object constructor]
        // ...
bar(S):
        // ...
        call    S::S(S&&) [complete object constructor]
        // ...

请参阅编译器资源管理器中的实时示例

作为解决方法,您应该使用:

return result_t(std::move(state.inheritingGroups), Result{});
// or
return {std::move(state.inheritingGroups), Result{}};
© www.soinside.com 2019 - 2024. All rights reserved.