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);
+}

Reply via email to