Правильно ли Clang-Tidy относительно std::move при построении std::tuple с использованием `{}`?

Я реализовал функцию на 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() в качестве аргумента константной ссылки; никакого движения на самом деле не произойдет

Чтобы решить эту проблему, я явно передал 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 используется двумя функциями), тем самым добавляя дополнительный уровень косвенности и потенциально заставляя будущих читателей искать его определение, теряя непосредственную ясность того, что это unordered_set.

В этом контексте у меня есть два основных вопроса:

  1. Прав ли Кланг-Тиди, заявляя, что в этом сценарии переезд на самом деле не произойдет?
  2. Верны ли мои подозрения относительно выбора нешаблонного конструктора для std::tuple при использовании {} в качестве аргумента?

Мне нужны идеи или альтернативные решения, которые сохранят ясность и простоту кода и при этом правильно устранят предупреждение Clang-Tidy. Любые предложения или объяснения высоко ценятся.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
62
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Да, clang-tidy — это правильно. std::tuple имеет много конструкторов, и поскольку вы используете {}, конструктор пересылки (который является шаблоном) не может быть вызван. Можно вызвать только конструктор, принимающий const Types&..., поскольку вычитание параметра шаблона из {} не требуется. Это конструктор (2) на cppreference.

В общем, из {} нельзя вывести ни один тип, поэтому конструктор пересылки нежизнеспособен. См. Почему автоматический вывод типов шаблонов и шаблонов различается для инициализаторов со скобками?.

Воспроизвести эту проблему можно следующим образом:

#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]
        // ...

См. живой пример в Compiler Explorer.

В качестве обходного пути вы должны использовать:

return result_t(std::move(state.inheritingGroups), Result{});
// or
return {std::move(state.inheritingGroups), Result{}};

Другие вопросы по теме