On Sat, 15 Nov 2025, David Malcolm wrote:
> In r15-6116-gd3dd24acd74605 I updated print_z_candidates to print a
> count of the number of candidates, and to show the number of each
> candidate in the list if there is more than one.
>
> The following patch updates print_candidates to work in a similar
> way, showing counts, numbering, and using nesting.
>
> Consider this test case for which we print 2 candidates:
>
> class foo
> {
> public:
> void test (int i, int j, void *ptr, int k);
> void test (int i, int j, int k);
> };
>
> // Wrong "const"-ness of a param, for one of the overloads (param 3).
> void foo::test (int i, int j, const void *ptr, int k)
> {
> }
>
> The output before the patch is:
>
> test.cc:9:6: error: no declaration matches ‘void foo::test(int, int, const
> void*, int)’
> 9 | void foo::test (int i, int j, const void *ptr, int k)
> | ^~~
> test.cc:5:8: note: candidates are: ‘void foo::test(int, int, int)’
> 5 | void test (int i, int j, int k);
> | ^~~~
> test.cc:4:8: note: ‘void foo::test(int, int, void*, int)’
> 4 | void test (int i, int j, void *ptr, int k);
> | ^~~~
> test.cc:1:7: note: ‘class foo’ defined here
> 1 | class foo
> | ^~~
>
> With the patch, the output looks like:
>
> test.cc:9:6: error: no declaration matches ‘void foo::test(int, int, const
> void*, int)’
> 9 | void foo::test (int i, int j, const void *ptr, int k)
> | ^~~
> • there are 2 candidates
> • candidate 1: ‘void foo::test(int, int, int)’
> test.cc:5:8:
> 5 | void test (int i, int j, int k);
> | ^~~~
> • candidate 2: ‘void foo::test(int, int, void*, int)’
> test.cc:4:8:
> 4 | void test (int i, int j, void *ptr, int k);
> | ^~~~
> test.cc:1:7: note: ‘class foo’ defined here
> 1 | class foo
> | ^~~
>
> which I believe is much more readable.
Agreed! The patch looks good to me overall, a few style suggestions
below, but I defer formal approval to Jason.
>
> I dabbled with removing the "there is 1 candidate" line for the case of
> a single candidate, but I think I prefer it to be present.
>
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
>
> OK for trunk?
> Dave
>
> gcc/cp/ChangeLog:
> * call.cc (print_z_candidates): Move inform_n call into a new
> inform_num_candidates function.
> * cp-tree.h (inform_num_candidates): New decl.
> * pt.cc (print_candidates_1): Drop, converting the looping part
> into...
> (flatten_candidates): ...this new function.
> (print_candidates): Use flatten_candidates to build an auto_vec
> of candidates, and use this to print them here, rather than in
> print_candidates_1. Eliminate the dynamic allocation of spaces
> for a prefix in favor of printing "candidate %i" when there is
> more than one candidate. Use inform_num_candidates to show a
> heading, and add nesting levels for it and for the candidate
> notes.
>
> gcc/testsuite/ChangeLog:
> * g++.dg/cpp0x/inline-ns2.C: Make dg-message directives non-empty.
> * g++.dg/cpp23/explicit-obj-lambda11.C: Prune the extra note.
> * g++.dg/diagnostic/bad-fndef-1.C: New test.
> * g++.dg/lookup/decl1.C: Give the dg-message directives different
> messages.
> * g++.dg/lookup/using17.C: Update expected output.
> * g++.dg/parse/non-dependent2.C: Likewise.
> * g++.old-deja/g++.other/lineno2.C: Give the dg-message directives
> different messages.
> * g++.old-deja/g++.pt/t37.C: Likewise.
> ---
> gcc/cp/call.cc | 5 +-
> gcc/cp/cp-tree.h | 1 +
> gcc/cp/pt.cc | 67 +++++++++++--------
> gcc/testsuite/g++.dg/cpp0x/inline-ns2.C | 18 ++---
> .../g++.dg/cpp23/explicit-obj-lambda11.C | 1 +
> gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C | 15 +++++
> gcc/testsuite/g++.dg/lookup/decl1.C | 8 +--
> gcc/testsuite/g++.dg/lookup/using17.C | 4 +-
> gcc/testsuite/g++.dg/parse/non-dependent2.C | 2 +-
> .../g++.old-deja/g++.other/lineno2.C | 8 +--
> gcc/testsuite/g++.old-deja/g++.pt/t37.C | 10 +--
> 11 files changed, 82 insertions(+), 57 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C
>
> diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
> index f80d597b33944..fc4e6da1f9a20 100644
> --- a/gcc/cp/call.cc
> +++ b/gcc/cp/call.cc
> @@ -4261,9 +4261,8 @@ print_z_candidates (location_t loc, struct z_candidate
> *candidates,
> ++num_candidates;
> }
>
> - inform_n (loc,
> - num_candidates, "there is %i candidate", "there are %i candidates",
> - num_candidates);
> + inform_num_candidates (loc, num_candidates);
> +
> auto_diagnostic_nesting_level sentinel2;
>
> int candidate_idx = 0;
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 5e8d1c9644c5b..d0d057c927017 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -7890,6 +7890,7 @@ extern tree maybe_process_partial_specialization (tree);
> extern tree most_specialized_instantiation (tree);
> extern tree most_specialized_partial_spec (tree, tsubst_flags_t, bool
> = false);
> extern tree most_constrained_function (tree);
> +extern void inform_num_candidates (location_t, int);
> extern void print_candidates (tree);
> extern void instantiate_pending_templates (int);
> extern tree tsubst_default_argument (tree, int, tree, tree,
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index bbbf49363e8f0..59e9585aac94d 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -2033,39 +2033,30 @@ explicit_class_specialization_p (tree type)
> return !uses_template_parms (CLASSTYPE_TI_ARGS (type));
> }
>
> -/* Print the list of functions at FNS, going through all the overloads
> - for each element of the list. Alternatively, FNS cannot be a
> - TREE_LIST, in which case it will be printed together with all the
> - overloads.
> -
> - MORE and *STR should respectively be FALSE and NULL when the function
> - is called from the outside. They are used internally on recursive
> - calls. print_candidates manages the two parameters and leaves NULL
> - in *STR when it ends. */
> +/* Populate OUT the list of functions at FNS, going through all the
> + overloads for each element of the list. Alternatively, FNS can be a
> + TREE_LIST, in which case it will be added together with all the
> + overloads. */
>
> static void
> -print_candidates_1 (tree fns, char **str, bool more = false)
> +flatten_candidates (tree fns, auto_vec<tree> &out)
> {
> if (TREE_CODE (fns) == TREE_LIST)
> for (; fns; fns = TREE_CHAIN (fns))
> - print_candidates_1 (TREE_VALUE (fns), str, more || TREE_CHAIN (fns));
> + flatten_candidates (TREE_VALUE (fns), out);
> else
> - for (lkp_iterator iter (fns); iter;)
> - {
> - tree cand = *iter;
> - ++iter;
> + for (lkp_iterator iter (fns); iter; ++iter)
Might as well use a range-based for while we're at it:
for (tree cand : lkp_range (fns))
> + out.safe_push (*iter);
> +}
>
> - const char *pfx = *str;
> - if (!pfx)
> - {
> - if (more || iter)
> - pfx = _("candidates are:");
> - else
> - pfx = _("candidate is:");
> - *str = get_spaces (pfx);
> - }
> - inform (DECL_SOURCE_LOCATION (cand), "%s %#qD", pfx, cand);
> - }
> +/* Print a note announcing a list of candidates. */
> +
> +void
> +inform_num_candidates (location_t loc, int num_candidates)
> +{
> + inform_n (loc,
> + num_candidates, "there is %i candidate", "there are %i candidates",
> + num_candidates);
> }
>
> /* Print the list of candidate FNS in an error message. FNS can also
> @@ -2074,9 +2065,27 @@ print_candidates_1 (tree fns, char **str, bool more =
> false)
> void
> print_candidates (tree fns)
> {
> - char *str = NULL;
> - print_candidates_1 (fns, &str);
> - free (str);
> + auto_vec<tree> candidates;
> + flatten_candidates (fns, candidates);
> +
> + auto_diagnostic_nesting_level sentinel;
> +
> + inform_num_candidates (UNKNOWN_LOCATION, candidates.length ());
> +
> + auto_diagnostic_nesting_level sentinel2;
> +
> + if (candidates.length () == 1)
> + {
> + tree cand = candidates[0];
> + inform (DECL_SOURCE_LOCATION (cand), "candidate is: %#qD", cand);
> + }
> + else
> + {
> + int idx = 0;
> + for (auto cand : candidates)
I prefer using 'tree' explicitly here instead of 'auto' since it's no
shorter.
> + inform (DECL_SOURCE_LOCATION (cand), "candidate %i: %#qD",
> + ++idx, cand);
> + }
> }
>
> /* Get a (possibly) constrained template declaration for the
> diff --git a/gcc/testsuite/g++.dg/cpp0x/inline-ns2.C
> b/gcc/testsuite/g++.dg/cpp0x/inline-ns2.C
> index 6ad9d65a6fbbb..1dc8efeb329ff 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/inline-ns2.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/inline-ns2.C
> @@ -2,17 +2,17 @@
>
> namespace Q {
> inline namespace V1 {
> - extern int i; // { dg-message "" }
> - extern int j; // { dg-message "" }
> - void f(); // { dg-message "" }
> - void g(); // { dg-message "" }
> + extern int i; // { dg-message "candidate" }
> + extern int j; // { dg-message "candidate" }
> + void f(); // { dg-message "candidate" }
> + void g(); // { dg-message "candidate" }
> }
> inline namespace V2 {
> - extern int j; // { dg-message "" }
> - void g(); // { dg-message "" }
> + extern int j; // { dg-message "candidate" }
> + void g(); // { dg-message "candidate" }
> }
> - extern int i; // { dg-message "" }
> - void f(); // { dg-message "" }
> + extern int i; // { dg-message "candidate" }
> + void f(); // { dg-message "candidate" }
> void h();
> }
> namespace R {
> @@ -22,4 +22,4 @@ int Q::i = 1; // { dg-error
> "ambiguous" }
> int Q::j = 1; // { dg-error "ambiguous" }
> void Q::f() { } // { dg-error "ambiguous" }
> void Q::g() { } // { dg-error "ambiguous" }
> -void R::h() { } // { dg-error "" }
> +void R::h() { } // { dg-error "should have been
> declared inside 'R'" }
> diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C
> b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C
> index 7957ad3e19430..1fc67d624714a 100644
> --- a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C
> +++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C
> @@ -45,3 +45,4 @@ void test2()
> // { dg-error "a lambda with captures may not have an explicit object
> parameter of an unrelated type" {depends on PR112874} { xfail *-*-* } t2_f }
> // { dg-note "candidate is" "" { target *-*-* } t2_f }
>
> +// { dg-prune-output "cc1plus: note: there is 1 candidate" }
> diff --git a/gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C
> b/gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C
> new file mode 100644
> index 0000000000000..1156072b11f16
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C
> @@ -0,0 +1,15 @@
> +class foo // { dg-message "'class foo' defined here" }
> +{
> +public:
> + void test (int i, int j, void *ptr, int k); // { dg-line close_decl }
> + void test (int i, int j, int k); // { dg-line other_decl }
> +};
> +
> +// Wrong "const"-ness of a param, for one of the overloads (param 3).
> +void foo::test (int i, int j, const void *ptr, int k) // { dg-line defn }
> +{
> +}
> +
> +// { dg-error "6: no declaration matches" "error" { target *-*-* } defn }
> +// { dg-message "8: candidate 1: " "candidate 1" { target *-*-* } other_decl
> }
> +// { dg-message "8: candidate 2: " "candidate 2" { target *-*-* } close_decl
> }
> diff --git a/gcc/testsuite/g++.dg/lookup/decl1.C
> b/gcc/testsuite/g++.dg/lookup/decl1.C
> index 205ffcff1d73d..38319ba923de0 100644
> --- a/gcc/testsuite/g++.dg/lookup/decl1.C
> +++ b/gcc/testsuite/g++.dg/lookup/decl1.C
> @@ -20,10 +20,10 @@ C2<X>::operator C1<Y>()
> }
>
> struct A { // { dg-message "defined here" }
> - operator int (); // { dg-message "operator" }
> - operator float (); // { dg-message "operator" }
> - operator float () const; // { dg-message "operator" }
> - template <typename T> operator T * (); // { dg-message "operator" }
> + operator int (); // { dg-message "operator int" }
> + operator float (); // { dg-message "operator float" }
> + operator float () const; // { dg-message "operator float" }
> + template <typename T> operator T * (); // { dg-message "operator T" }
> };
>
> A::operator short () { // { dg-error "no declaration matches" }
> diff --git a/gcc/testsuite/g++.dg/lookup/using17.C
> b/gcc/testsuite/g++.dg/lookup/using17.C
> index 55875fe9af978..1e69dbe36376d 100644
> --- a/gcc/testsuite/g++.dg/lookup/using17.C
> +++ b/gcc/testsuite/g++.dg/lookup/using17.C
> @@ -3,11 +3,11 @@
> // { dg-do compile }
>
> namespace M {
> - struct S {}; // { dg-message "candidates are: .struct M::S." "candidate 1"
> }
> + struct S {}; // { dg-message "candidates 1: 'struct M::S'"}
> }
>
> int S;
> -struct S {}; // { dg-message ".struct S." "candidate 2" }
> +struct S {}; // { dg-message "candidate 2: 'struct S'" }
>
> using namespace M;
>
> diff --git a/gcc/testsuite/g++.dg/parse/non-dependent2.C
> b/gcc/testsuite/g++.dg/parse/non-dependent2.C
> index c22497044e90c..eb83390206c52 100644
> --- a/gcc/testsuite/g++.dg/parse/non-dependent2.C
> +++ b/gcc/testsuite/g++.dg/parse/non-dependent2.C
> @@ -15,7 +15,7 @@ struct Foo {
> struct Baz
> {
> int j;
> - int k; // { dg-message "candidates" }
> + int k; // { dg-message "candidate" }
>
> };
>
> diff --git a/gcc/testsuite/g++.old-deja/g++.other/lineno2.C
> b/gcc/testsuite/g++.old-deja/g++.other/lineno2.C
> index d6aca8b9cfd08..6434f9ac45ce4 100644
> --- a/gcc/testsuite/g++.old-deja/g++.other/lineno2.C
> +++ b/gcc/testsuite/g++.old-deja/g++.other/lineno2.C
> @@ -2,14 +2,14 @@
> // Submitted by Nathan Sidwell <[email protected]>
> // Bug: g++ wasn't listing candidates for a failed conversion.
>
> -void f(int, double); // { dg-message "" } candidate
> -void f(double, int); // { dg-message "" } candidate
> -void f(int); // { dg-message "" } candidate
> +void f(int, double); // { dg-message "candidate" }
> +void f(double, int); // { dg-message "candidate" }
> +void f(int); // { dg-message "candidate" }
>
> int
> main ()
> {
> void (*ptr)(int, int);
>
> - ptr = &f; // { dg-error "" } no match
> + ptr = &f; // { dg-error "no matches" }
> }
> diff --git a/gcc/testsuite/g++.old-deja/g++.pt/t37.C
> b/gcc/testsuite/g++.old-deja/g++.pt/t37.C
> index dbf1f4403b31c..ffef5e58a4385 100644
> --- a/gcc/testsuite/g++.old-deja/g++.pt/t37.C
> +++ b/gcc/testsuite/g++.old-deja/g++.pt/t37.C
> @@ -1,14 +1,14 @@
> // { dg-do compile }
>
> -class A { // { dg-message "A::A" } synthesized copy ctor
> - // { dg-message "defined here" "note" { target *-*-* } .-1 }
> +class A { // { dg-message "A::A\\\(const A&\\\)" } synthesized copy ctor
> + // { dg-message "'class A' defined here" "note" { target *-*-* } .-1 }
> public:
> - A(int); // { dg-message "A::A" }
> - A(float); // { dg-message "A::A" }
> + A(int); // { dg-message "A::A\\\(int\\\)" }
> + A(float); // { dg-message "A::A\\\(float\\\)" }
> ~A();
> };
>
> -A::A() { // { dg-error "" }
> +A::A() { // { dg-error "no declaration matches" }
> }
>
> A::A(int) {
> --
> 2.26.3
>
>