Bug 53281 - poor error message for calling a non-const method from a const object
Summary: poor error message for calling a non-const method from a const object
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 4.6.3
: P3 enhancement
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic
Depends on:
Blocks:
 
Reported: 2012-05-08 13:25 UTC by Rui Maciel
Modified: 2022-05-26 11:09 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2012-05-09 00:00:00


Attachments
Incomplete patch (1.22 KB, patch)
2022-05-26 11:09 UTC, Jonathan Wakely
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Rui Maciel 2012-05-08 13:25:07 UTC
Consider the following code:

<code>
class Foo {
        void bar1() {}
        void bar2(Foo const &foo) {
                foo.bar1();
        }   
};

int main()
{
        return 0;
}
</code>

When this code is compiled with g++, the following error message is shown:

<message>
main.c++: In member function ‘const void Foo::bar2(const Foo&)’:
main.c++:4:26: error: passing ‘const Foo’ as ‘this’ argument of ‘const void Foo::bar1()’ discards qualifiers [-fpermissive]
</message>

The error message is technically correct.  Yet, it is too cryptic and a bit unhelpful, to the point it may be considered that the point of this error message is entirely missed.  

As an alternative, it would be nice if g++ displayed an error message which would actually be straight-forward and provided a clear description of the problem at hand, such as "trying to call a non-const method from a const object".
Comment 1 Rui Maciel 2012-05-08 13:27:24 UTC
The same suggestion applies to the cases where a non-const method is called from a const method, such as in the example below:

<code>
class Foo {
        void bar1() {}
        void bar2() const {
                bar1();
        }
};

int main()
{
        return 0;
}
</code>

The same error message is returned:
<message>
main.c++: In member function ‘void Foo::bar2() const’:
main.c++:4:22: error: passing ‘const Foo’ as ‘this’ argument of ‘void Foo::bar1()’ discards qualifiers [-fpermissive]
</message>
Comment 2 Jonathan Wakely 2012-05-09 15:59:09 UTC
confirmed (but any change to the diagnostic should not mention the word  "method" because C++ has member functions not methods)
Comment 3 Jonathan Wakely 2018-03-06 13:41:57 UTC
Current trunk gives:

cv.cc: In member function ‘void Foo::bar2(const Foo&)’:
cv.cc:4:26: error: passing ‘const Foo’ as ‘this’ argument discards qualifiers [-fpermissive]
                 foo.bar1();
                          ^
cv.cc:2:14: note:   in call to ‘void Foo::bar1()’
         void bar1() {}
              ^~~~


With this patch:

--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -7799,7 +7799,24 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
              if (permerror (input_location, "passing %qT as %<this%> "
                             "argument discards qualifiers",
                             TREE_TYPE (argtype)))
-               inform (DECL_SOURCE_LOCATION (fn), "  in call to %qD", fn);
+               {
+                 int argquals = cp_type_quals (TREE_TYPE (argtype));
+                 int fnquals = cp_type_quals (TREE_TYPE (fn));
+                 int discarded = argquals & ~fnquals;
+                 bool non_const = discarded & TYPE_QUAL_CONST;
+                 bool non_volatile = discarded & TYPE_QUAL_VOLATILE;
+                 const char *descr;
+                 if (non_const && non_volatile)
+                   descr = " which is non-const and non-volatile";
+                 else if (non_const)
+                   descr = " which is non-const";
+                 else if (non_volatile)
+                   descr = " which is non-volatile";
+                 else
+                   descr = "";
+                 inform (DECL_SOURCE_LOCATION (fn), "  in call to %qD%s",
+                         fn, descr);
+               }
            }
          else
            return error_mark_node;


we get:

cv.cc: In member function ‘void Foo::bar2(const Foo&)’:
cv.cc:4:26: error: passing ‘const Foo’ as ‘this’ argument discards qualifiers [-fpermissive]
                 foo.bar1();
                          ^
cv.cc:2:14: note:   in call to ‘void Foo::bar1()’ which is non-const
         void bar1() {}
              ^~~~

Note the addition of "which is non-const" to the note.
Comment 4 Jonathan Wakely 2018-03-06 13:56:29 UTC
FWIW Clang says:

cv.cc:4:17: error: member function 'bar1' not viable: 'this' argument has type 'const Foo', but function is not marked const
                foo.bar1();
                ^~~
cv.cc:2:14: note: 'bar1' declared here
        void bar1() {}
             ^
1 error generated.


And EDG says:

"cv.cc", line 4: error: the object has type qualifiers that are not compatible
          with the member function
            object type is: const Foo
                  foo.bar1();
                  ^

1 error detected in the compilation of "cv.cc".
Comment 5 Jonathan Wakely 2018-03-06 14:34:34 UTC
A further improvement might be to stop talking about "passing '...' as 'this' argument" since that's a leaky abstraction: although member functions are implemented with a hidden 'this' parameter, that's not how most C++ programmers think of it, or how the abstract machine is described. Also, 'this' is a pointer, and 'const Foo' is not a pointer type (Clang loses points there too).

So maybe this would be better:

--- a/gcc/cp/call.c
+++ b/gcc/cp/call.c
@@ -7796,8 +7796,15 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
        {
          if (complain & tf_error)
            {
-             if (permerror (input_location, "passing %qT as %<this%> "
-                            "argument discards qualifiers",
+             int argquals = cp_type_quals (TREE_TYPE (argtype));
+             int fnquals = cp_type_quals (TREE_TYPE (fn));
+             int discarded = argquals & ~fnquals;
+             bool non_const = discarded & TYPE_QUAL_CONST;
+             bool non_volatile = discarded & TYPE_QUAL_VOLATILE;
+             if (permerror (input_location, "cannot call %s%s member "
+                            "function on object of type %qT",
+                            non_const ? "non-const" : "",
+                            non_volatile ? "non-volatile" : "",
                             TREE_TYPE (argtype)))
                inform (DECL_SOURCE_LOCATION (fn), "  in call to %qD", fn);
            }


cv.cc: In member function 'void Foo::bar2(const Foo&)':
cv.cc:4:26: error: cannot call non-const member function on object of type 'const Foo' [-fpermissive]
                 foo.bar1();
                          ^
cv.cc:2:14: note:   in call to 'void Foo::bar1()'
         void bar1() {}
              ^~~~
Comment 6 David Malcolm 2018-03-18 12:32:37 UTC
Another example, given by reddit user "agcpp" here:
https://www.reddit.com/r/cpp/comments/84op5c/usability_improvements_in_gcc_8/dvvry8y/

> I'm little late to the party but want to add another example
> where diagnostics will improve overall experience -
> https://godbolt.org/g/kGKowz
>
> The diagnostics produced by MSVC here are just perfect, clang does
> seem a bit verbose but readable otoh gcc confuses anyone who
> hasn't been writing c++ for about 5 years :D

************************************************************

Code at that godbolt link:

#include <string>
using std::string;

void func(string &s) {
    s.clear();
}

int main() {
    const string s = "20";
    func(s);
}

************************************************************

MSVC 19 2017:

/opt/compiler-explorer/windows/19.10.25017/lib/native/include/xlocale(314): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc
<source>(10): error C2664: 'void func(std::string &)': cannot convert argument 1 from 'const std::string' to 'std::string &'
<source>(10): note: Conversion loses qualifiers

************************************************************

clang trunk:

<source>:10:5: error: no matching function for call to 'func'
    func(s);
    ^~~~
<source>:4:6: note: candidate function not viable: 1st argument ('const std::__cxx11::string' (aka 'const basic_string<char>')) would lose const qualifier
void func(string &s) {
     ^
1 error generated.
Compiler returned: 1

************************************************************

gcc trunk:

<source>: In function 'int main()':
<source>:10:10: error: binding reference of type 'std::__cxx11::string&' {aka 'std::__cxx11::basic_string<char>&'} to 'const string' {aka 'const std::__cxx11::basic_string<char>'} discards qualifiers
     func(s);
          ^
<source>:4:6: note:   initializing argument 1 of 'void func(std::__cxx11::string&)'
 void func(string &s) {
      ^~~~
Compiler returned: 1

************************************************************

Part of the verbosity here relates to PR c++/84897 (reporting std::string rather than std::__cxx11::string).

We're emitting a 28 words/~170 chars to seemingly talk about how:

  Compiler: "did you know that std::string is actually a
  	    std::basic_string?"  mumble mumble, something
	    about a qualifier.

where the user has to spot the most pertinent word: "const" appearing in the 2nd pair of types, absent in the 1st pair of types.

Among other issues, the aka seems redundant/unhelpful here.

It seems best to special-case the "const member fn/param called with non-const" cases and to report them directly in terms of the types in user's source with and without "const", and to be explicit about which qualifier(s) are lost.

We should also underline the param, or the "const" token.

Proposed output for the above:

error: binding reference of type 'std::string&' to 'const string' discards 'const' qualifier
     func(s);
          ^
note:   initializing non-'const' argument 1 of 'void func(std::string&)'
 void func(string &s) {
           ~~~~~~~^~

(though even that is a bit "compiler-ese"; might want to talk about it being a call up-front)

Proposed output for the case in comment #3:

cv.cc: In member function ‘void Foo::bar2(const Foo&)’:
cv.cc:4:26: error: passing ‘const Foo’ as ‘this’ argument discards 'const' qualifier [-fpermissive]
                 foo.bar1();
                          ^
cv.cc:2:14: note:   in call to ‘void Foo::bar1()’ which is non-const
         void bar1() {}
              ^~~~

I hope to fix these for gcc 9.
Comment 7 David Malcolm 2018-03-18 12:37:04 UTC
(In reply to David Malcolm from comment #6)
[...]
> We should also underline the param, or the "const" token.
(actually, if the member function or param is 'const', it's not a problem, I think.  -ENOCOFFEE)
Comment 8 Jonathan Wakely 2018-03-19 13:24:53 UTC
(In reply to David Malcolm from comment #6)
> It seems best to special-case the "const member fn/param called with
> non-const" cases and to report them directly in terms of the types in user's
> source with and without "const", and to be explicit about which qualifier(s)
> are lost.

Don't forget 'volatile', which is rarely used for member function qualifiers, but we might as well handle both at once (as my patches above do).


> Proposed output for the case in comment #3:
> 
> cv.cc: In member function ‘void Foo::bar2(const Foo&)’:
> cv.cc:4:26: error: passing ‘const Foo’ as ‘this’ argument discards 'const'
> qualifier [-fpermissive]
>                  foo.bar1();
>                           ^
> cv.cc:2:14: note:   in call to ‘void Foo::bar1()’ which is non-const
>          void bar1() {}
>               ^~~~

This has a number of problems, see comment 5 for discussion and a patch.
Comment 9 Jonathan Wakely 2018-05-29 17:32:56 UTC
N.B. comment 6 is the subject of PR 85958 which is really a different bug to this one. This is about invalid member function calls on cv-qualified objects, and 85958 is about invalid attempts to bind references to cv-qualified objects.
Comment 10 Jonathan Wakely 2018-05-29 22:06:57 UTC
My patch fails to account for ref-qualifiers on the member function, so needs some improvement.
Comment 11 Jakub Jelinek 2019-05-03 09:15:38 UTC
GCC 9.1 has been released.
Comment 12 Jakub Jelinek 2019-08-12 08:54:49 UTC
GCC 9.2 has been released.
Comment 13 Jakub Jelinek 2020-03-12 11:58:38 UTC
GCC 9.3.0 has been released, adjusting target milestone.
Comment 14 Barry Revzin 2022-05-24 23:30:20 UTC
Following up on this (and a lot of the ideas presented here are I think better than "passing [...] discards qualifiers")...

This:

struct B {
    void f();
}; 

struct D : B {
    void const_f() const {
        f();
    }
};

gives me that same error:

<source>:8:10: error: passing 'const D' as 'this' argument discards qualifiers [-fpermissive]
    8 |         f();
      |         ~^~
<source>:2:10: note:   in call to 'void B::f()'
    2 |     void f();
      |          ^

But this slight difference (D is now a template, though B is a non-dependent base):

struct B {
    void f();
}; 

template <class T>
struct D : B {
    void const_f() const {
        f();
    }
};

instead emits: 

<source>:8:10: error: cannot convert 'const D<T>*' to 'B*'
    8 |         f();
      |         ~^~
<source>:2:10: note:   initializing argument 'this' of 'void B::f()'
    2 |     void f();
      |          ^

Which is... technically correct, that is the problem, but now that I'm used to seeing the "discards qualifiers" error, this one really threw me for a loop. 

It'd be nice at the very least if these two examples gave the same error
Comment 15 Jonathan Wakely 2022-05-26 11:09:58 UTC
Created attachment 53035 [details]
Incomplete patch