我正在用 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
的直接清晰度。
在这种情况下,我有两个主要问题:
std::tuple
作为参数时,我对 {}
选择非模板构造函数的怀疑是否正确?我正在寻找见解或替代解决方案,以保持代码的清晰度和简单性,同时正确解决 Clang-Tidy 警告。任何建议或解释都非常感谢。
是的,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{}};