[PATCH] D129951: [clang] teaches Clang the special ADL rules for functions in std::ranges

2022-07-17 Thread Christopher Di Bella via Phabricator via cfe-commits
cjdb added inline comments.



Comment at: clang/lib/Sema/SemaOverload.cpp:9438
   // FIXME: Pass in the explicit template arguments?
   ArgumentDependentLookup(Name, Loc, Args, Fns);
 

rsmith wrote:
> It would seem preferable to me to do the filtering in 
> `ArgumentDependentLookup` itself rather than here (but I don't feel strongly 
> about it).
Agreed. I tried to do it there, but was having issues getting it working. I'll 
give it a second go?



Comment at: clang/lib/Sema/SemaOverload.cpp:9455-9459
+// Functions in 'std::ranges' are hidden from ADL per [range.iter.ops]/2 
and
+// [algorithms.requirements]/2.
+if ((*I)->isInStdRangesNamespace() &&
+Name.getNameKind() == DeclarationName::NameKind::Identifier)
+  continue;

rsmith wrote:
> I worry that this might break some stdlib implementation that finds helper 
> functions via ADL in `std::ranges` somehow. Also, it seems desirable to make 
> this opt-in for the stdlib implementation and indeed for end-user functions 
> not in `std::ranges`.
> 
> Have you considered enabling this behavior with an attribute instead of by 
> detecting whether the function is in `std::ranges`?
You're the second person to have suggested this, and I daresay Aaron will too, 
based on our prior discussion about this. We'd chatted about `__disable_adl` 
specifically, so that anyone who uses it won't be affected by a silent change 
from Clang to GCC: they'd instead get a break. I would prefer an attribute too.

My main concern is that other stdlib implementers would object to adding yet 
another annotation to their function calls (based on my failure to get libc++ 
to be as aggressive as Microsoft/STL is with `[[nodiscard]]`).



Comment at: clang/lib/Sema/SemaOverload.cpp:12837-12841
+  // Functions in 'std::ranges' inhibit ADL per [range.iter.ops]/2 and
+  // [algorithms.requirements]/2.
+  if (!ULE->decls().empty() && ULE->decls_begin()->isInStdRangesNamespace() &&
+  ULE->getName().getNameKind() == DeclarationName::NameKind::Identifier)
+return;

rsmith wrote:
> What should happen if a `using` declaration names one of these things? Should 
> we care about the properties of the underlying declaration (eg, whether the 
> target of the using declaration is in this namespace / has the attribute), or 
> about the found decl (eg, whether the `using` declaration itself is in the 
> namespace / has the attribute)?
> 
> Depending on the answer, we may need to check all declarations in the 
> `UnresolvedLookupExpr`, not only the first one. For example, we could have an 
> overload set that contains both a user-declared function and a `using` 
> declaration that names a function in `std::ranges` here.
> When found by unqualified ([basic.lookup.unqual]) name lookup for the 
> postfix-expression in a function call ([expr.call]), they inhibit 
> argument-dependent name lookup.

My interpretation of this is that both using-declarations and -directives are 
impacted (regardless of what the code says right now). [basic.lookup.unqual] 
doesn't specifically say anything about using-declarations, but I think 
[p4](https://eel.is/c++draft/basic.lookup.unqual#4) implies that they're 
included.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D129951/new/

https://reviews.llvm.org/D129951

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D129951: [clang] teaches Clang the special ADL rules for functions in std::ranges

2022-07-17 Thread Richard Smith - zygoloid via Phabricator via cfe-commits
rsmith added inline comments.



Comment at: clang/lib/Sema/SemaOverload.cpp:9438
   // FIXME: Pass in the explicit template arguments?
   ArgumentDependentLookup(Name, Loc, Args, Fns);
 

It would seem preferable to me to do the filtering in `ArgumentDependentLookup` 
itself rather than here (but I don't feel strongly about it).



Comment at: clang/lib/Sema/SemaOverload.cpp:9455-9459
+// Functions in 'std::ranges' are hidden from ADL per [range.iter.ops]/2 
and
+// [algorithms.requirements]/2.
+if ((*I)->isInStdRangesNamespace() &&
+Name.getNameKind() == DeclarationName::NameKind::Identifier)
+  continue;

I worry that this might break some stdlib implementation that finds helper 
functions via ADL in `std::ranges` somehow. Also, it seems desirable to make 
this opt-in for the stdlib implementation and indeed for end-user functions not 
in `std::ranges`.

Have you considered enabling this behavior with an attribute instead of by 
detecting whether the function is in `std::ranges`?



Comment at: clang/lib/Sema/SemaOverload.cpp:12837-12841
+  // Functions in 'std::ranges' inhibit ADL per [range.iter.ops]/2 and
+  // [algorithms.requirements]/2.
+  if (!ULE->decls().empty() && ULE->decls_begin()->isInStdRangesNamespace() &&
+  ULE->getName().getNameKind() == DeclarationName::NameKind::Identifier)
+return;

What should happen if a `using` declaration names one of these things? Should 
we care about the properties of the underlying declaration (eg, whether the 
target of the using declaration is in this namespace / has the attribute), or 
about the found decl (eg, whether the `using` declaration itself is in the 
namespace / has the attribute)?

Depending on the answer, we may need to check all declarations in the 
`UnresolvedLookupExpr`, not only the first one. For example, we could have an 
overload set that contains both a user-declared function and a `using` 
declaration that names a function in `std::ranges` here.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D129951/new/

https://reviews.llvm.org/D129951

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D129951: [clang] teaches Clang the special ADL rules for functions in std::ranges

2022-07-17 Thread Christopher Di Bella via Phabricator via cfe-commits
cjdb added a comment.

In D129951#3657923 , @cjdb wrote:

> ~~Looking at the output from Clang 14 , I'm 
> observing that a binary with 178 function templates is 13% the size of the 
> one with 89 function objects. When only one function object is used vs all 
> 178 function templates, the functions still win out, with the binary being 
> 80% the size.~~

I wrote this quite late at night and confused total lines of assembly with 
program size. That's not accurate at all.

There doesn't seem to be a difference between the two when the optimiser is 
enabled, but with:

- `-O0`: there's a binary size difference of 24kB.
- `-Og -g`: there's a binary size difference of 32kB.
- `-g`: there's a binary size difference of 59kB.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D129951/new/

https://reviews.llvm.org/D129951

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D129951: [clang] teaches Clang the special ADL rules for functions in std::ranges

2022-07-17 Thread Christopher Di Bella via Phabricator via cfe-commits
cjdb added a comment.

Looking at the output from Clang 14 , I'm 
observing that a binary with 178 function templates is 13% the size of the one 
with 89 function objects. When only one function object is used vs all 178 
function templates, the functions still win out, with the binary being 80% the 
size.

The AST also becomes substantially larger.

I ran `time clang++-13 -c file.cpp -std=c++20 -Oz -DNDEBUG` locally, a hundred 
times, and got the following results:

  # Function objects
  real mean:0.07447
  real median:  0.074
  real stddev:  0.002267268191

  sys mean: 0.01664
  sys median:   0.016
  sys stddev:   0.005569614534

  user mean:0.05785
  user median:  0.057
  user stddev:  0.005848896987

  # Function templates
  real mean:0.06336
  real median:  0.063
  real stddev:  0.002076905235

  sys mean: 0.01701
  sys median:   0.017
  sys stddev:   0.005545942188

  user mean:0.04645
  user median:  0.047
  user stddev:  0.005678908346


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D129951/new/

https://reviews.llvm.org/D129951

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[PATCH] D129951: [clang] teaches Clang the special ADL rules for functions in std::ranges

2022-07-16 Thread Christopher Di Bella via Phabricator via cfe-commits
cjdb updated this revision to Diff 445286.
cjdb edited the summary of this revision.
cjdb added a comment.

updates an inaccuracy in the commit message


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D129951/new/

https://reviews.llvm.org/D129951

Files:
  clang/include/clang/AST/DeclBase.h
  clang/lib/AST/DeclBase.cpp
  clang/lib/Sema/SemaOverload.cpp
  clang/test/SemaCXX/disable-adl.cpp

Index: clang/test/SemaCXX/disable-adl.cpp
===
--- /dev/null
+++ clang/test/SemaCXX/disable-adl.cpp
@@ -0,0 +1,94 @@
+// RUN: %clang_cc1 %s -fsyntax-only -verify
+
+// expected-n...@disable-adl.cpp:* 2{{}}
+
+namespace std {
+  struct S1 {};
+  S1 inhibited(S1);
+
+  namespace ranges {
+struct S2 {};
+void hidden(S2);
+int inhibited(S1);
+  }
+}
+
+void test_functions() {
+  hidden(std::ranges::S2{});
+  // expected-error@-1{{use of undeclared identifier 'hidden'; did you mean 'std::ranges::hidden'?}}
+
+  using namespace std::ranges;
+  int x = inhibited(std::S1{}); // no error
+}
+
+namespace std {
+  template
+  S1 inhibited_template(T);
+
+  namespace ranges {
+template
+void hidden_template(T);
+
+template
+int inhibited_template(T);
+  }
+}
+
+void test_function_templates() {
+  hidden_template(std::ranges::S2{});
+  // expected-error@-1{{use of undeclared identifier 'hidden_template'; did you mean 'std::ranges::hidden_template'?}}
+
+  using namespace std::ranges;
+  int x = inhibited_template(std::S1{});
+}
+
+namespace std {
+  S1 inhibited_mixed(S1);
+
+  namespace ranges {
+template
+int inhibited_mixed(T);
+  }
+}
+
+void test_mixed() {
+  using namespace std::ranges;
+  int x = inhibited_mixed(std::S1{});
+}
+
+// Should be covered by the hidden functions checks, but just to be sure.
+void test_ranges_hidden() {
+  {
+std::S1 a = inhibited(std::S1{});
+std::S1 b = inhibited_template(std::S1{});
+std::S1 c = inhibited_mixed(std::S1{});
+  }
+  {
+using namespace std;
+std::S1 a = inhibited(std::S1{});
+std::S1 b = inhibited_template(std::S1{});
+std::S1 c = inhibited_mixed(std::S1{});
+  }
+}
+
+namespace std {
+  namespace ranges {
+void operator-(S2);
+
+struct hidden_friend_operator {
+  friend void operator-(hidden_friend_operator i, int) {}
+};
+
+struct hidden_friend_swap {
+  friend void swap(hidden_friend_swap, hidden_friend_swap) {}
+};
+  }
+}
+
+void test_friends_and_operators() {
+  -std::ranges::S2{};// no error
+  std::ranges::hidden_friend_operator{} - 1; // no error
+
+  swap(std::ranges::hidden_friend_swap{}, std::ranges::hidden_friend_swap{});
+  // expected-error@-1{{use of undeclared identifier 'swap'}}
+}
Index: clang/lib/Sema/SemaOverload.cpp
===
--- clang/lib/Sema/SemaOverload.cpp
+++ clang/lib/Sema/SemaOverload.cpp
@@ -9452,6 +9452,12 @@
   for (ADLResult::iterator I = Fns.begin(), E = Fns.end(); I != E; ++I) {
 DeclAccessPair FoundDecl = DeclAccessPair::make(*I, AS_none);
 
+// Functions in 'std::ranges' are hidden from ADL per [range.iter.ops]/2 and
+// [algorithms.requirements]/2.
+if ((*I)->isInStdRangesNamespace() &&
+Name.getNameKind() == DeclarationName::NameKind::Identifier)
+  continue;
+
 if (FunctionDecl *FD = dyn_cast(*I)) {
   if (ExplicitTemplateArgs)
 continue;
@@ -9650,7 +9656,7 @@
   const OverloadCandidate ,
   const OverloadCandidate ) {
   // FIXME: Per P2113R0 we also need to compare the template parameter lists
-  // when comparing template functions. 
+  // when comparing template functions.
   if (Cand1.Function && Cand2.Function && Cand1.Function->hasPrototype() &&
   Cand2.Function->hasPrototype()) {
 auto *PT1 = cast(Cand1.Function->getFunctionType());
@@ -12828,6 +12834,12 @@
CandidateSet, PartialOverloading,
/*KnownValid*/ true);
 
+  // Functions in 'std::ranges' inhibit ADL per [range.iter.ops]/2 and
+  // [algorithms.requirements]/2.
+  if (!ULE->decls().empty() && ULE->decls_begin()->isInStdRangesNamespace() &&
+  ULE->getName().getNameKind() == DeclarationName::NameKind::Identifier)
+return;
+
   if (ULE->requiresADL())
 AddArgumentDependentLookupCandidates(ULE->getName(), ULE->getExprLoc(),
  Args, ExplicitTemplateArgs,
Index: clang/lib/AST/DeclBase.cpp
===
--- clang/lib/AST/DeclBase.cpp
+++ clang/lib/AST/DeclBase.cpp
@@ -396,6 +396,11 @@
   return DC && DC->isStdNamespace();
 }
 
+bool Decl::isInStdRangesNamespace() const {
+  const DeclContext *DC = getDeclContext();
+  return DC && DC->isStdRangesNamespace();
+}
+
 TranslationUnitDecl 

[PATCH] D129951: [clang] teaches Clang the special ADL rules for functions in std::ranges

2022-07-16 Thread Christopher Di Bella via Phabricator via cfe-commits
cjdb updated this revision to Diff 445285.
cjdb added a comment.

moves test from Sema to SemaCXX

I Noticed that SemaCXX is only a test directory, and that there's no 
corresponding SemaCXX in either include or lib.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D129951/new/

https://reviews.llvm.org/D129951

Files:
  clang/include/clang/AST/DeclBase.h
  clang/lib/AST/DeclBase.cpp
  clang/lib/Sema/SemaOverload.cpp
  clang/test/SemaCXX/disable-adl.cpp

Index: clang/test/SemaCXX/disable-adl.cpp
===
--- /dev/null
+++ clang/test/SemaCXX/disable-adl.cpp
@@ -0,0 +1,94 @@
+// RUN: %clang_cc1 %s -fsyntax-only -verify
+
+// expected-n...@disable-adl.cpp:* 2{{}}
+
+namespace std {
+  struct S1 {};
+  S1 inhibited(S1);
+
+  namespace ranges {
+struct S2 {};
+void hidden(S2);
+int inhibited(S1);
+  }
+}
+
+void test_functions() {
+  hidden(std::ranges::S2{});
+  // expected-error@-1{{use of undeclared identifier 'hidden'; did you mean 'std::ranges::hidden'?}}
+
+  using namespace std::ranges;
+  int x = inhibited(std::S1{}); // no error
+}
+
+namespace std {
+  template
+  S1 inhibited_template(T);
+
+  namespace ranges {
+template
+void hidden_template(T);
+
+template
+int inhibited_template(T);
+  }
+}
+
+void test_function_templates() {
+  hidden_template(std::ranges::S2{});
+  // expected-error@-1{{use of undeclared identifier 'hidden_template'; did you mean 'std::ranges::hidden_template'?}}
+
+  using namespace std::ranges;
+  int x = inhibited_template(std::S1{});
+}
+
+namespace std {
+  S1 inhibited_mixed(S1);
+
+  namespace ranges {
+template
+int inhibited_mixed(T);
+  }
+}
+
+void test_mixed() {
+  using namespace std::ranges;
+  int x = inhibited_mixed(std::S1{});
+}
+
+// Should be covered by the hidden functions checks, but just to be sure.
+void test_ranges_hidden() {
+  {
+std::S1 a = inhibited(std::S1{});
+std::S1 b = inhibited_template(std::S1{});
+std::S1 c = inhibited_mixed(std::S1{});
+  }
+  {
+using namespace std;
+std::S1 a = inhibited(std::S1{});
+std::S1 b = inhibited_template(std::S1{});
+std::S1 c = inhibited_mixed(std::S1{});
+  }
+}
+
+namespace std {
+  namespace ranges {
+void operator-(S2);
+
+struct hidden_friend_operator {
+  friend void operator-(hidden_friend_operator i, int) {}
+};
+
+struct hidden_friend_swap {
+  friend void swap(hidden_friend_swap, hidden_friend_swap) {}
+};
+  }
+}
+
+void test_friends_and_operators() {
+  -std::ranges::S2{};// no error
+  std::ranges::hidden_friend_operator{} - 1; // no error
+
+  swap(std::ranges::hidden_friend_swap{}, std::ranges::hidden_friend_swap{});
+  // expected-error@-1{{use of undeclared identifier 'swap'}}
+}
Index: clang/lib/Sema/SemaOverload.cpp
===
--- clang/lib/Sema/SemaOverload.cpp
+++ clang/lib/Sema/SemaOverload.cpp
@@ -9452,6 +9452,12 @@
   for (ADLResult::iterator I = Fns.begin(), E = Fns.end(); I != E; ++I) {
 DeclAccessPair FoundDecl = DeclAccessPair::make(*I, AS_none);
 
+// Functions in 'std::ranges' are hidden from ADL per [range.iter.ops]/2 and
+// [algorithms.requirements]/2.
+if ((*I)->isInStdRangesNamespace() &&
+Name.getNameKind() == DeclarationName::NameKind::Identifier)
+  continue;
+
 if (FunctionDecl *FD = dyn_cast(*I)) {
   if (ExplicitTemplateArgs)
 continue;
@@ -9650,7 +9656,7 @@
   const OverloadCandidate ,
   const OverloadCandidate ) {
   // FIXME: Per P2113R0 we also need to compare the template parameter lists
-  // when comparing template functions. 
+  // when comparing template functions.
   if (Cand1.Function && Cand2.Function && Cand1.Function->hasPrototype() &&
   Cand2.Function->hasPrototype()) {
 auto *PT1 = cast(Cand1.Function->getFunctionType());
@@ -12828,6 +12834,12 @@
CandidateSet, PartialOverloading,
/*KnownValid*/ true);
 
+  // Functions in 'std::ranges' inhibit ADL per [range.iter.ops]/2 and
+  // [algorithms.requirements]/2.
+  if (!ULE->decls().empty() && ULE->decls_begin()->isInStdRangesNamespace() &&
+  ULE->getName().getNameKind() == DeclarationName::NameKind::Identifier)
+return;
+
   if (ULE->requiresADL())
 AddArgumentDependentLookupCandidates(ULE->getName(), ULE->getExprLoc(),
  Args, ExplicitTemplateArgs,
Index: clang/lib/AST/DeclBase.cpp
===
--- clang/lib/AST/DeclBase.cpp
+++ clang/lib/AST/DeclBase.cpp
@@ -396,6 +396,11 @@
   return DC && DC->isStdNamespace();
 }
 
+bool Decl::isInStdRangesNamespace() const {
+  const DeclContext *DC = getDeclContext();
+  return DC && 

[PATCH] D129951: [clang] teaches Clang the special ADL rules for functions in std::ranges

2022-07-16 Thread Christopher Di Bella via Phabricator via cfe-commits
cjdb created this revision.
cjdb added a reviewer: aaron.ballman.
Herald added a project: All.
cjdb requested review of this revision.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.

Per per [range.iter.ops]/2 and [algorithms.requirements]/2, functions
declared in the namespace 'std::ranges' aren't found by ADL, and
suppress ADL when they're called in an unqualified context (e.g. a
using-directive).

Libraries have been implementing these functions as function objects
with varying rules (e.g. libc++ and Microsoft/STL both try their best to
make the function objects appear as standard library function templates,
while libstdc++ makes them plain function objects).

Having a large number of types typically has a negative impact on both
compile-times and progam size, and there are approximately 130 of these
at present. Furthermore, the diagnostics can be marginally improved by
switching to proper functions, which will make it clearer that the
problem is at the user level, rather than the implementation level (see
https://godbolt.org/z/1sYMsxdfM for an example).

By making it possible to implement ranges functions as functions, it's
hoped that the library will be incentivised to migrate over.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D129951

Files:
  clang/include/clang/AST/DeclBase.h
  clang/lib/AST/DeclBase.cpp
  clang/lib/Sema/SemaOverload.cpp
  clang/test/Sema/disable-adl.cpp

Index: clang/test/Sema/disable-adl.cpp
===
--- /dev/null
+++ clang/test/Sema/disable-adl.cpp
@@ -0,0 +1,94 @@
+// RUN: %clang_cc1 %s -fsyntax-only -verify
+
+// expected-n...@disable-adl.cpp:* 2{{}}
+
+namespace std {
+  struct S1 {};
+  S1 inhibited(S1);
+
+  namespace ranges {
+struct S2 {};
+void hidden(S2);
+int inhibited(S1);
+  }
+}
+
+void test_functions() {
+  hidden(std::ranges::S2{});
+  // expected-error@-1{{use of undeclared identifier 'hidden'; did you mean 'std::ranges::hidden'?}}
+
+  using namespace std::ranges;
+  int x = inhibited(std::S1{}); // no error
+}
+
+namespace std {
+  template
+  S1 inhibited_template(T);
+
+  namespace ranges {
+template
+void hidden_template(T);
+
+template
+int inhibited_template(T);
+  }
+}
+
+void test_function_templates() {
+  hidden_template(std::ranges::S2{});
+  // expected-error@-1{{use of undeclared identifier 'hidden_template'; did you mean 'std::ranges::hidden_template'?}}
+
+  using namespace std::ranges;
+  int x = inhibited_template(std::S1{});
+}
+
+namespace std {
+  S1 inhibited_mixed(S1);
+
+  namespace ranges {
+template
+int inhibited_mixed(T);
+  }
+}
+
+void test_mixed() {
+  using namespace std::ranges;
+  int x = inhibited_mixed(std::S1{});
+}
+
+// Should be covered by the hidden functions checks, but just to be sure.
+void test_ranges_hidden() {
+  {
+std::S1 a = inhibited(std::S1{});
+std::S1 b = inhibited_template(std::S1{});
+std::S1 c = inhibited_mixed(std::S1{});
+  }
+  {
+using namespace std;
+std::S1 a = inhibited(std::S1{});
+std::S1 b = inhibited_template(std::S1{});
+std::S1 c = inhibited_mixed(std::S1{});
+  }
+}
+
+namespace std {
+  namespace ranges {
+void operator-(S2);
+
+struct hidden_friend_operator {
+  friend void operator-(hidden_friend_operator i, int) {}
+};
+
+struct hidden_friend_swap {
+  friend void swap(hidden_friend_swap, hidden_friend_swap) {}
+};
+  }
+}
+
+void test_friends_and_operators() {
+  -std::ranges::S2{};// no error
+  std::ranges::hidden_friend_operator{} - 1; // no error
+
+  swap(std::ranges::hidden_friend_swap{}, std::ranges::hidden_friend_swap{});
+  // expected-error@-1{{use of undeclared identifier 'swap'}}
+}
Index: clang/lib/Sema/SemaOverload.cpp
===
--- clang/lib/Sema/SemaOverload.cpp
+++ clang/lib/Sema/SemaOverload.cpp
@@ -9452,6 +9452,12 @@
   for (ADLResult::iterator I = Fns.begin(), E = Fns.end(); I != E; ++I) {
 DeclAccessPair FoundDecl = DeclAccessPair::make(*I, AS_none);
 
+// Functions in 'std::ranges' are hidden from ADL per [range.iter.ops]/2 and
+// [algorithms.requirements]/2.
+if ((*I)->isInStdRangesNamespace() &&
+Name.getNameKind() == DeclarationName::NameKind::Identifier)
+  continue;
+
 if (FunctionDecl *FD = dyn_cast(*I)) {
   if (ExplicitTemplateArgs)
 continue;
@@ -9650,7 +9656,7 @@
   const OverloadCandidate ,
   const OverloadCandidate ) {
   // FIXME: Per P2113R0 we also need to compare the template parameter lists
-  // when comparing template functions. 
+  // when comparing template functions.
   if (Cand1.Function && Cand2.Function && Cand1.Function->hasPrototype() &&
   Cand2.Function->hasPrototype()) {
 auto *PT1 = cast(Cand1.Function->getFunctionType());
@@