I was implementing a function in C++, specifically a callable class object, designed to return a std::tuple containing a container of objects and a result. After some adjustments (see below) I have the definition of a member function:
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), {});
}
... where State is:
struct State
{
std::unordered_set<QnUuid> inheritingGroups{};
std::unordered_set<QnUuid> visited{};
std::unordered_set<QnUuid> visiting{};
};
Clang-Tidy gives me warnings on both return statements:
Clang-Tidy: Passing result of std::move() as a const reference argument; no move will actually happen
To resolve this, I explicitly passed Result() in the second return statement:
return result_t(std::move(state.inheritingGroups), Result());
I suspect this might be due to a non-templated constructor of std::tuple being selected when {} is used as one of the arguments. Ideally, I am aiming for a simplified return syntax, such as:
// 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), {}};
However, initializing the tuple with {...} results in compilation issues due to ambiguous constructor calls.
In an attempt to address this, I introduced a typedef alias, but it led to a more verbose and, in my view, less clean version:
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());
I find the introduction of the groups alias to be a suboptimal solution as it only serves to circumvent the Clang-Tidy warning and introduces unnecessary complexity. The alias must be declared outside the immediate scope (State is used by 2 functions), thereby adding an extra layer of indirection and potentially causing future readers to look up its definition, losing the immediate clarity of it being an unordered_set.
With this context, I have two primary questions:
- Is Clang-Tidy accurate in stating that the move will not actually occur in this scenario?
- Is my suspicion regarding the selection of a non-template constructor for
std::tuplewhen using{}as an argument correct?
I am looking for insights or alternative solutions that maintain code clarity and simplicity while properly addressing the Clang-Tidy warning. Any suggestions or explanations are highly appreciated.