c++tuplesmove-semanticsperfect-forwardingclang-tidy

Clang-tidy bugprone-use-after-move with perfect forwarding


clang-tidy reports the following piece of code as bugprone-use-after-move

template <typename F, typename Tuple, size_t... I>
auto transform_tuple_impl(F&& f, Tuple&& tuple, std::index_sequence<I...>)
{
    return std::make_tuple(f(std::get<I>(std::forward<Tuple>(tuple)))...);
}

template <typename F, typename Tuple>
auto transform_tuple(F&& f, Tuple&& t)
{
    constexpr size_t size = std::tuple_size<std::decay_t<Tuple>>::value;
    return transform_tuple_impl(std::forward<F>(f), std::forward<Tuple>(t), std::make_index_sequence<size>{});
}

With the this message:

error: 'tuple' used after it was forwarded [bugprone-use-after-move,-warnings-as-errors]
  |             return std::make_tuple(f(std::get<I>(std::forward<Tuple>(tuple)))...);
  |                                                                      ^
note: forward occurred here
  |             return std::make_tuple(f(std::get<I>(std::forward<Tuple>(tuple)))...);
  |                                      ^
note: the use and forward are unsequenced, i.e. there is no guarantee about the order in which they are evaluated
  |             return std::make_tuple(f(std::get<I>(std::forward<Tuple>(tuple)))...);
  |                                                                      ^

I'm using C++14 so std::get should have the overloaded operator for Rvalue reference. Would there be a way to enforce the order of evaluation? Is this a false-positive?


Solution

  • For an actual std::tuple this is safe, because of the exact specification of std::get for std::tuple and the fact that you std::get and pass on each tuple element exactly once.

    But for other types Tuple it may not be and if you didn't pass a proper index sequence to the function (e.g. one with repeating indices), then the diagnostic would be correct as well.

    You are not constraining Tuple to be a std::tuple and even if you did I have some doubts that clang-tidy's check is written clever enough to take that constraint and the special known behavior of std::get for std::tuple into account. Also, the behavior is only correct given that a proper index sequence is passed, which is also probably too complex of an analysis to make for clang-tidy.

    Order of evaluation isn't really all that relevant. The main problem that clang-tidy reports is either way that you pass std::forward<Tuple>(tuple) multiple times to functions, regardless of order. Which in general is wrong and only correct in special cases where move semantics of a type/function are specified more strictly than usual.