提问人:Sergey Kolesnik 提问时间:9/29/2023 更新时间:9/29/2023 访问量:72
使用“{}”构造 std::tuple 时,Clang-Tidy 关于 std::move 是否正确?
Is Clang-Tidy correct regarding std::move when constructing std::tuple using `{}`?
问:
我正在用 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());
我发现引入别名是一个次优解决方案,因为它只能规避 Clang-Tidy 警告并引入不必要的复杂性。别名必须在直接作用域之外声明(由 2 个函数使用),从而增加了额外的间接层,并可能导致未来的读者查找其定义,从而失去它是 .groups
State
unordered_set
在这种情况下,我有两个主要问题:
- Clang-Tidy 是否准确地指出在这种情况下实际上不会发生此举?
- 我在使用
{}
作为参数时对std::tuple
选择非模板构造函数的怀疑是否正确?
我正在寻找见解或替代解决方案,以保持代码的清晰度和简单性,同时正确解决 Clang-Tidy 警告。任何建议或解释都非常感谢。
答:
是的,clang-tidy 是正确的。 有很多构造函数,并且由于您使用的是 ,因此无法调用转发构造函数(这是一个模板)。
只能调用构造函数,因为不需要从中扣除模板参数。
这是 cppreference 上的构造函数 (2)。std::tuple
{}
const Types&...
{}
通常,无法从中推断出任何类型,这就是转发构造函数不可行的原因。请参阅为什么支撑初始值设定项的自动和模板类型扣除不同?。{}
您可以按如下方式重现此问题:
#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{}};
评论