On Sun, Aug 26, 2018 at 09:57:32AM +1000, Jason Merrill wrote: > On Sat, Aug 25, 2018 at 5:34 AM, Marek Polacek <pola...@redhat.com> wrote: > > On Fri, Aug 24, 2018 at 11:32:18PM +1000, Jason Merrill wrote: > >> On Fri, Aug 24, 2018 at 12:53 AM, Marek Polacek <pola...@redhat.com> wrote: > >> > On Thu, Aug 23, 2018 at 10:44:30AM -0400, Marek Polacek wrote: > >> >> +T > >> >> +fn3 (const T t) > >> >> +{ > >> >> + // t is const: will decay into move. > >> >> + return t; > >> >> +} > >> >> + > >> >> +T > >> >> +fn4 (const T t) > >> >> +{ > >> >> + // t is const: will decay into move despite std::move, so it's > >> >> redundant. > >> >> + return std::move (t); // { dg-warning "redundant move in return > >> >> statement" } > >> >> +} > >> > > >> > This should read "decay into copy". We can't move from a const object. > >> > Consider > >> > it fixed. > >> > >> Well, we'll do the overload resolution as though t were an rvalue, > >> even though it's const; it's just unlikely to succeed, since a > >> constructor taking a const rvalue reference doesn't make much sense. > > > > Ah, true. I guess that's only used in like std::reference_wrapper. > > > >> It occurs to me that the std::move might not be redundant in some > >> cases: for the implicit treatment as an rvalue, the return must select > >> a constructor that takes an rvalue reference to the returned object's > >> type. With an explict std::move, that restriction doesn't apply. So, > >> for > >> > >> struct C { }; > >> struct A { > >> operator C() &; > >> operator C() &&; > >> }; > >> > >> C f(A a) > >> { > >> return a; // calls operator C()& > >> return std::move(a); // calls operator C()&& > >> } > >> > >> ...though I see we currently get the first return wrong, and call the > >> rvalue overload for both. I think there was a recent core issue in > >> this area, I'll try to find that later. > > > > You're right. Wow, I did not think of this. I went looking for that DR but > > I'm not finding it, please do let me know if you find it. Does that mean > > that > > treat_lvalue_as_rvalue_p will have to change? > > No, the problem is with the LOOKUP_PREFER_RVALUE block in > build_over_call, which fails to consider "the object's type". > > > Do you want me to open a PR? > > Please.
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87109 > > @@ -9185,7 +9198,9 @@ can_do_nrvo_p (tree retval, tree functype) > > static void > > maybe_warn_pessimizing_move (tree retval, tree functype) > > { > > - if (!warn_pessimizing_move) > > + location_t loc = cp_expr_loc_or_loc (retval, input_location); > > + > > + if (!(warn_pessimizing_move || warn_redundant_move)) > > return; > > Let's actually set loc after the early return. OK with that change. This is what I committed; thanks. 2018-08-26 Marek Polacek <pola...@redhat.com> PR c++/87029, Implement -Wredundant-move. * c.opt (Wredundant-move): New option. * typeck.c (treat_lvalue_as_rvalue_p): New function. (maybe_warn_pessimizing_move): Call convert_from_reference. Warn about redundant moves. * doc/invoke.texi: Document -Wredundant-move. * g++.dg/cpp0x/Wredundant-move1.C: New test. * g++.dg/cpp0x/Wredundant-move2.C: New test. * g++.dg/cpp0x/Wredundant-move3.C: New test. * g++.dg/cpp0x/Wredundant-move4.C: New test. diff --git gcc/c-family/c.opt gcc/c-family/c.opt index 76840dd77ad..31a2b972919 100644 --- gcc/c-family/c.opt +++ gcc/c-family/c.opt @@ -985,6 +985,10 @@ Wredundant-decls C ObjC C++ ObjC++ Var(warn_redundant_decls) Warning Warn about multiple declarations of the same object. +Wredundant-move +C++ ObjC++ Var(warn_redundant_move) Warning LangEnabledBy(C++ ObjC++,Wextra) +Warn about redundant calls to std::move. + Wregister C++ ObjC++ Var(warn_register) Warning Warn about uses of register storage specifier. diff --git gcc/cp/typeck.c gcc/cp/typeck.c index 24647e29a55..fa3ba715698 100644 --- gcc/cp/typeck.c +++ gcc/cp/typeck.c @@ -9178,6 +9178,19 @@ can_do_nrvo_p (tree retval, tree functype) && !TYPE_VOLATILE (TREE_TYPE (retval))); } +/* Returns true if we should treat RETVAL, an expression being returned, + as if it were designated by an rvalue. See [class.copy.elision]. */ + +static bool +treat_lvalue_as_rvalue_p (tree retval) +{ + return ((cxx_dialect != cxx98) + && ((VAR_P (retval) && !DECL_HAS_VALUE_EXPR_P (retval)) + || TREE_CODE (retval) == PARM_DECL) + && DECL_CONTEXT (retval) == current_function_decl + && !TREE_STATIC (retval)); +} + /* Warn about wrong usage of std::move in a return statement. RETVAL is the expression we are returning; FUNCTYPE is the type the function is declared to return. */ @@ -9185,9 +9198,11 @@ can_do_nrvo_p (tree retval, tree functype) static void maybe_warn_pessimizing_move (tree retval, tree functype) { - if (!warn_pessimizing_move) + if (!(warn_pessimizing_move || warn_redundant_move)) return; + location_t loc = cp_expr_loc_or_loc (retval, input_location); + /* C++98 doesn't know move. */ if (cxx_dialect < cxx11) return; @@ -9212,14 +9227,24 @@ maybe_warn_pessimizing_move (tree retval, tree functype) STRIP_NOPS (arg); if (TREE_CODE (arg) == ADDR_EXPR) arg = TREE_OPERAND (arg, 0); + arg = convert_from_reference (arg); /* Warn if we could do copy elision were it not for the move. */ if (can_do_nrvo_p (arg, functype)) { auto_diagnostic_group d; - if (warning_at (location_of (retval), OPT_Wpessimizing_move, + if (warning_at (loc, OPT_Wpessimizing_move, "moving a local object in a return statement " "prevents copy elision")) - inform (location_of (retval), "remove %<std::move%> call"); + inform (loc, "remove %<std::move%> call"); + } + /* Warn if the move is redundant. It is redundant when we would + do maybe-rvalue overload resolution even without std::move. */ + else if (treat_lvalue_as_rvalue_p (arg)) + { + auto_diagnostic_group d; + if (warning_at (loc, OPT_Wredundant_move, + "redundant move in return statement")) + inform (loc, "remove %<std::move%> call"); } } } @@ -9499,11 +9524,7 @@ check_return_expr (tree retval, bool *no_warning) Note that these conditions are similar to, but not as strict as, the conditions for the named return value optimization. */ bool converted = false; - if ((cxx_dialect != cxx98) - && ((VAR_P (retval) && !DECL_HAS_VALUE_EXPR_P (retval)) - || TREE_CODE (retval) == PARM_DECL) - && DECL_CONTEXT (retval) == current_function_decl - && !TREE_STATIC (retval) + if (treat_lvalue_as_rvalue_p (retval) /* This is only interesting for class type. */ && CLASS_TYPE_P (functype)) { diff --git gcc/doc/invoke.texi gcc/doc/invoke.texi index e4148297a87..985ef9f3510 100644 --- gcc/doc/invoke.texi +++ gcc/doc/invoke.texi @@ -231,7 +231,7 @@ in the following sections. -Wdelete-non-virtual-dtor -Wdeprecated-copy -Wliteral-suffix @gol -Wmultiple-inheritance @gol -Wnamespaces -Wnarrowing @gol --Wpessimizing-move @gol +-Wpessimizing-move -Wredundant-move @gol -Wnoexcept -Wnoexcept-type -Wclass-memaccess @gol -Wnon-virtual-dtor -Wreorder -Wregister @gol -Weffc++ -Wstrict-null-sentinel -Wtemplates @gol @@ -3158,6 +3158,49 @@ But in this example, the @code{std::move} call prevents copy elision. This warning is enabled by @option{-Wall}. +@item -Wno-redundant-move @r{(C++ and Objective-C++ only)} +@opindex Wredundant-move +@opindex Wno-redundant-move +This warning warns about redundant calls to @code{std::move}; that is, when +a move operation would have been performed even without the @code{std::move} +call. This happens because the compiler is forced to treat the object as if +it were an rvalue in certain situations such as returning a local variable, +where copy elision isn't applicable. Consider: + +@smallexample +struct T @{ +@dots{} +@}; +T fn(T t) +@{ + @dots{} + return std::move (t); +@} +@end smallexample + +Here, the @code{std::move} call is redundant. Because G++ implements Core +Issue 1579, another example is: + +@smallexample +struct T @{ // convertible to U +@dots{} +@}; +struct U @{ +@dots{} +@}; +U fn() +@{ + T t; + @dots{} + return std::move (t); +@} +@end smallexample +In this example, copy elision isn't applicable because the type of the +expression being returned and the function return type differ, yet G++ +treats the return value as if it were designated by an rvalue. + +This warning is enabled by @option{-Wextra}. + @item -fext-numeric-literals @r{(C++ and Objective-C++ only)} @opindex fext-numeric-literals @opindex fno-ext-numeric-literals @@ -4112,6 +4155,7 @@ name is still supported, but the newer name is more descriptive.) -Wold-style-declaration @r{(C only)} @gol -Woverride-init @gol -Wsign-compare @r{(C only)} @gol +-Wredundant-move @r{(only for C++)} @gol -Wtype-limits @gol -Wuninitialized @gol -Wshift-negative-value @r{(in C++03 and in C99 and newer)} @gol diff --git gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C index e69de29bb2d..5d4a25dbc3b 100644 --- gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C +++ gcc/testsuite/g++.dg/cpp0x/Wredundant-move1.C @@ -0,0 +1,106 @@ +// PR c++/87029 +// { dg-do compile { target c++11 } } +// { dg-options "-Wredundant-move" } + +// Define std::move. +namespace std { + template<typename _Tp> + struct remove_reference + { typedef _Tp type; }; + + template<typename _Tp> + struct remove_reference<_Tp&> + { typedef _Tp type; }; + + template<typename _Tp> + struct remove_reference<_Tp&&> + { typedef _Tp type; }; + + template<typename _Tp> + constexpr typename std::remove_reference<_Tp>::type&& + move(_Tp&& __t) noexcept + { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); } +} + +struct T { + T() { } + T(const T&) { } + T(T&&) { } +}; + +struct U { + U() { } + U(const U&) { } + U(U&&) { } + U(T) { } +}; + +T +fn1 (T t) +{ + return t; +} + +T +fn2 (T t) +{ + // Will use move even without std::move. + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +T +fn3 (const T t) +{ + // t is const: will decay into copy. + return t; +} + +T +fn4 (const T t) +{ + // t is const: will decay into copy despite std::move, so it's redundant. + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +int +fn5 (int i) +{ + // Not a class type. + return std::move (i); +} + +T +fn6 (T t, bool b) +{ + if (b) + throw std::move (t); + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +U +fn7 (T t) +{ + // Core 1579 means we'll get a move here. + return t; +} + +U +fn8 (T t) +{ + // Core 1579 means we'll get a move here. Even without std::move. + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +T +fn9 (T& t) +{ + // T is a reference and the move isn't redundant. + return std::move (t); +} + +T +fn10 (T&& t) +{ + // T is a reference and the move isn't redundant. + return std::move (t); +} diff --git gcc/testsuite/g++.dg/cpp0x/Wredundant-move2.C gcc/testsuite/g++.dg/cpp0x/Wredundant-move2.C index e69de29bb2d..f181afeeb84 100644 --- gcc/testsuite/g++.dg/cpp0x/Wredundant-move2.C +++ gcc/testsuite/g++.dg/cpp0x/Wredundant-move2.C @@ -0,0 +1,57 @@ +// PR c++/87029 +// { dg-do compile { target c++11 } } +// { dg-options "-Wredundant-move" } + +// Define std::move. +namespace std { + template<typename _Tp> + struct remove_reference + { typedef _Tp type; }; + + template<typename _Tp> + struct remove_reference<_Tp&> + { typedef _Tp type; }; + + template<typename _Tp> + struct remove_reference<_Tp&&> + { typedef _Tp type; }; + + template<typename _Tp> + constexpr typename std::remove_reference<_Tp>::type&& + move(_Tp&& __t) noexcept + { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); } +} + +struct T { }; +struct U { U(T); }; + +template<typename Tp> +T +fn1 (T t) +{ + // Non-dependent type. + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +template<typename Tp1, typename Tp2> +Tp1 +fn2 (Tp2 t) +{ + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +template<typename Tp1, typename Tp2> +Tp1 +fn3 (Tp2 t) +{ + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +int +main () +{ + T t; + fn1<T>(t); + fn2<T, T>(t); + fn3<U, T>(t); +} diff --git gcc/testsuite/g++.dg/cpp0x/Wredundant-move3.C gcc/testsuite/g++.dg/cpp0x/Wredundant-move3.C index e69de29bb2d..7084134e370 100644 --- gcc/testsuite/g++.dg/cpp0x/Wredundant-move3.C +++ gcc/testsuite/g++.dg/cpp0x/Wredundant-move3.C @@ -0,0 +1,43 @@ +// PR c++/87029 +// { dg-do compile { target c++11 } } +// { dg-options "-Wredundant-move" } + +// Define std::move. +namespace std { + template<typename _Tp> + struct remove_reference + { typedef _Tp type; }; + + template<typename _Tp> + struct remove_reference<_Tp&> + { typedef _Tp type; }; + + template<typename _Tp> + struct remove_reference<_Tp&&> + { typedef _Tp type; }; + + template<typename _Tp> + constexpr typename std::remove_reference<_Tp>::type&& + move(_Tp&& __t) noexcept + { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); } +} + +struct T { }; + +T +fn1 (T t) +{ + return (1, std::move (t)); +} + +T +fn2 (T t) +{ + return [&](){ return std::move (t); }(); +} + +T +fn3 (T t) +{ + return [=](){ return std::move (t); }(); +} diff --git gcc/testsuite/g++.dg/cpp0x/Wredundant-move4.C gcc/testsuite/g++.dg/cpp0x/Wredundant-move4.C index e69de29bb2d..aa89e46de99 100644 --- gcc/testsuite/g++.dg/cpp0x/Wredundant-move4.C +++ gcc/testsuite/g++.dg/cpp0x/Wredundant-move4.C @@ -0,0 +1,86 @@ +// PR c++/87029 +// { dg-do compile { target c++11 } } +// { dg-options "-Wredundant-move" } + +// Define std::move. +namespace std { + template<typename _Tp> + struct remove_reference + { typedef _Tp type; }; + + template<typename _Tp> + struct remove_reference<_Tp&> + { typedef _Tp type; }; + + template<typename _Tp> + struct remove_reference<_Tp&&> + { typedef _Tp type; }; + + template<typename _Tp> + constexpr typename std::remove_reference<_Tp>::type&& + move(_Tp&& __t) noexcept + { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); } +} + +struct T { + T() { } + T(const T&) { } + T(T&&) { } +}; + +struct U { + U() { } + U(const U&) { } + U(U&&) { } + U(T) { } +}; + +U +fn1 (T t, bool b) +{ + if (b) + return t; + else + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +U +fn2 (bool b) +{ + T t; + if (b) + return t; + else + return std::move (t); // { dg-warning "redundant move in return statement" } +} + +U +fn3 (bool b) +{ + static T t; + if (b) + return t; + else + return std::move (t); +} + +T g; + +U +fn4 (bool b) +{ + if (b) + return g; + else + return std::move (g); +} + +long int +fn5 (bool b) +{ + int i = 42; + if (b) + return i; + else + return std::move (i); +}