This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] Forwarding reference overload in constructors
ClosedPublic

Authored by leanil on Mar 2 2017, 12:15 PM.

Details

Summary

Perfect forwarding constructors are called instead of copy constructors if the forwarding reference provides a closer match (e.g. with non-const parameter). This can be confusing to developers unfamiliar with this feature.

class Person {
public:
  // perfect forwarding ctor
  template<typename T>
  explicit Person(T&& n) {}

  // (possibly compiler generated) copy ctor
  Person(const Person& rhs); 
};

For detailed explanation of the issue, see Scott Meyers' Effective Modern C++, Item 26.

Running the checker on the LLVM source produces warnings for the following constructors:

https://github.com/llvm-mirror/llvm/blob/master/include/llvm/ADT/iterator.h#L287
https://github.com/llvm-mirror/llvm/blob/master/include/llvm/ADT/iterator_range.h#L39
https://github.com/llvm-mirror/llvm/blob/master/include/llvm/Support/Allocator.h#L150

These are probably not errors, but the described issue does occur (i.e. the perfect forwarding constructor can be called instead of the default copy), so it may worth a double-check.

Diff Detail

Repository
rL LLVM

Event Timeline

leanil created this revision.Mar 2 2017, 12:15 PM
leanil added inline comments.Mar 2 2017, 12:20 PM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
22 ↗(On Diff #90367)

This check could probably be simpler, but I wanted to be more effective than just using QualType.getAsString().

please add the check in the release notes as well.

added my thoughts. i like the check, seems really thought out :)

docs/clang-tidy/checks/misc-forwarding-reference-overload.rst
36 ↗(On Diff #90367)

maybe "can act like described above" should be different.
i think being more explicit with the hiding/unexpected call to those is clearer.

test/clang-tidy/misc-forwarding-reference-overload.cpp
21 ↗(On Diff #90367)

could the check output a note when there was a userdefined constructor and point to that one if it gets hidden? that would make it clearer, what and how the problem occurs.

28 ↗(On Diff #90367)

maybe be more explicit with whats Ok with a comment // Ok or sth like that on all following constructors?

Eugene.Zelenko set the repository for this revision to rL LLVM.
Eugene.Zelenko added a subscriber: Prazek.
aaron.ballman added inline comments.Mar 2 2017, 10:31 PM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
23 ↗(On Diff #90367)

This should be CheckTemplate per our usual coding conventions.

39 ↗(On Diff #90367)

Please use auto * (or const auto `).

44 ↗(On Diff #90367)

Same here.

119 ↗(On Diff #90367)

I think this can be lowered into the for loop as auto Iter = Ctor->param_begin() + 1.

EricWF added a subscriber: EricWF.Mar 2 2017, 10:43 PM
leanil updated this revision to Diff 90569.Mar 4 2017, 1:42 AM

Added changes according to the comments.

leanil marked 6 inline comments as done.Mar 4 2017, 1:51 AM
leanil added inline comments.
test/clang-tidy/misc-forwarding-reference-overload.cpp
21 ↗(On Diff #90367)

What do you mean by "if it gets hidden"? Should I look for specific calls that are hijacked by the perfect forwarding ctor? Or just make a note on the user defined copy/move ctors any time I produce a warning (without looking at actual calls)?
I think the former would be quite tricky to do.
Also, what if the perfect forwarding ctor hides the compiler generated copy/move? Should I still make a note (maybe pointing to the class itself)?

JonasToth added inline comments.Mar 4 2017, 4:08 AM
docs/clang-tidy/checks/misc-forwarding-reference-overload.rst
38 ↗(On Diff #90569)

typo: prefect -> perfect

test/clang-tidy/misc-forwarding-reference-overload.cpp
21 ↗(On Diff #90367)
class Person {
public:
  // perfect forwarding ctor
  template<typename T>
  explicit Person(T&& n) {}

  // (possibly compiler generated) copy ctor
  Person(const Person& rhs); 
};

a note pointing to a user defined ctor would be good i think.
with compiler generated operations the warning is clear.

emitting such a note would be a minor feature and if too complicated not worth it, i guess.

aaron.ballman requested changes to this revision.Mar 8 2017, 8:19 PM
aaron.ballman added inline comments.
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
31 ↗(On Diff #90569)

This should be using equals() rather than find() -- otherwise a name like enable_if_awesome() would trigger the match. You should also make sure the name is in the ::std namespace so that you can't trigger it with foo::enable_if or foo::std::enable_if.

35 ↗(On Diff #90569)

Reference types as well? Or is that not sensible?

53 ↗(On Diff #90569)

This seems generally useful and should probably be in ASTMatchers.h. However, that can be done in a follow-up patch.

118 ↗(On Diff #90569)

I don't think this if statement is required -- Ctor should always be found, no?

125–126 ↗(On Diff #90569)

I think a better diagnostic might be: "constructor accepting a universal reference hides the %select{copy|move|both the copy and move}0 %select{constructor{|s}1"

And then provide a note ("%select{copy|move}0 constructor declared here") that points to the offending copy and/or move constructor.

clang-tidy/misc/MiscTidyModule.cpp
70 ↗(On Diff #90569)

I wonder if the name might be slightly misleading -- I've always understood these to be universal references rather than forwarding references. I don't have the Meyers book in front of me -- what nomenclature does he use?

Once we pick the name, we should use it consistently in the source code (like the file name for the check and the check name), the documentation, and the release notes.

This revision now requires changes to proceed.Mar 8 2017, 8:19 PM
leanil updated this revision to Diff 91144.Mar 9 2017, 1:16 AM
leanil edited edge metadata.

Fix enable_if detection in pointer and reference types.
Improve test coverage for these cases.

leanil marked 3 inline comments as done.Mar 9 2017, 1:50 AM
leanil added inline comments.
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
31 ↗(On Diff #90569)

My intention with using find() was that if a user has an enable_if_awesome(), then it seems more likely to be similar to the std version, than to have completely different purpose.
And as a bonus find() also finds std::enable_if_t in one check.

125–126 ↗(On Diff #90569)

Without checking actual constructor calls, I would have to make notes on every (non disabled) copy / move constructor, any time I produce a warning. And as the warning already states where the problem lies, the notes would only help people find the copy and move constructors. Do you think that's necessary?

clang-tidy/misc/MiscTidyModule.cpp
70 ↗(On Diff #90569)

Meyers calls them universal references, but it's forwarding reference in the standard (14.8.2.1).

aaron.ballman added inline comments.Mar 12 2017, 10:48 AM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
31 ↗(On Diff #90569)

We don't typically use a heuristic approach like that -- instead, we often let the user specify their own list of names explicitly, as an option.

125–126 ↗(On Diff #90569)

The warning states where the forwarding reference constructor is, but it doesn't state where the conflicting constructors are. When we issue diagnostics like that, we generally use notes so that the user can see all of the locations involved -- the user may want to get rid of the other constructors, or they may want to get rid of the forwarding reference constructor. Also, saying "can hide" implies that it isn't hiding anything at all, just that it's possible to do so. Tightening up the wording and showing all of the locations involved solves both problems.

clang-tidy/misc/MiscTidyModule.cpp
70 ↗(On Diff #90569)

Hmm, the terms are a bit too new to really get a good idea from google's ngram viewer, but the search result counts are:

Google:
"universal reference" : 270,000
"forwarding reference" : 9650

Stack Overflow:
universal reference : 3016
forwarding reference: 1654

So I think that these are probably more well-known as universal references, despite the standard's nomenclature being forwarding reference.

clang-tidy/misc/MiscTidyModule.cpp
70 ↗(On Diff #90569)

The Q&A section in https://isocpp.org/files/papers/N4164.pdf explains why "universal reference" is a bad name.

xazax.hun added inline comments.Mar 13 2017, 2:38 AM
clang-tidy/misc/MiscTidyModule.cpp
70 ↗(On Diff #90569)

I think in a compiler related project at least in the source code we should stick the the standard's nomenclature. In the documentation, however, it is worth to mention that, this very same thing is called universal reference by Scott Meyers.

aaron.ballman added inline comments.Mar 13 2017, 4:47 AM
clang-tidy/misc/MiscTidyModule.cpp
70 ↗(On Diff #90569)

Okay, I'm convinced enough that "forward reference" is okay. :-)

leanil updated this revision to Diff 92542.Mar 21 2017, 2:04 PM

Suppress warning only on std::enable_if.
Make note on copy and move constructors.

leanil marked 15 inline comments as done.Mar 21 2017, 2:05 PM
aaron.ballman added inline comments.Mar 22 2017, 6:35 AM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
125–126 ↗(On Diff #90569)

This isn't quite complete. It's still an ambiguous statement to say "it can hide"; it does hide these constructors, and we even know which ones. Emit the notes before you emit the main diagnostic and you can use the %select suggested originally to be specific in the diagnostic.

127–128 ↗(On Diff #92542)

You can use a range-based for loop here instead.

129–137 ↗(On Diff #92542)

This can be simplified by using a %select as well.

docs/clang-tidy/checks/misc-forwarding-reference-overload.rst
48 ↗(On Diff #92542)

This is no longer accurate.

xazax.hun added inline comments.Mar 22 2017, 6:46 AM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
125–126 ↗(On Diff #90569)

We can not say for sure without looking at a concrete call whether a constructor is "hidden" or not. It is always determined during the overload resolution.

This check does not consider the calls, because that way it would always miss the possible misuses if libraries.

aaron.ballman added inline comments.Mar 22 2017, 7:01 AM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
125–126 ↗(On Diff #90569)

I can see the logic in that. I guess I'm thinking of it the same way we use the phrase "hidden" when describing code like:

struct Base {
  virtual void f(int);
};

struct Derived : Base {
  void f(double);
};

We claim Derived::f() hides Base::f() without considering the callers.

xazax.hun added inline comments.Mar 22 2017, 7:05 AM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
125–126 ↗(On Diff #90569)

I see. In that case maybe we should come up with a less confusing term like hijacking overload? The constructors are still part of the overload set, so no hiding as in the standard's nomenclature happens here, but the overload resolution is not doing what the user would expect in these cases.

aaron.ballman added inline comments.Mar 22 2017, 7:17 AM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
125–126 ↗(On Diff #90569)

I'm also fine going back to being somewhat more wishy-washy in our phrasing (can hide).

What do you think about using the %select to specify what can be hidden, rather than always talking about copy and move constructors (one of which may not even be present in the user's source)?

leanil updated this revision to Diff 92765.Mar 23 2017, 12:22 AM

Simplify the note generation and correct the documentation.

leanil marked 3 inline comments as done.Mar 23 2017, 12:29 AM
leanil added inline comments.
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
125–126 ↗(On Diff #90569)

I think the (assumed) use of a compiler generated copy or move is similarly problematic as in the case of a user defined one, so I would keep mentioning both.

aaron.ballman added inline comments.Mar 23 2017, 5:58 AM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
127 ↗(On Diff #92765)

Should be const auto * rather than just auto.

131 ↗(On Diff #92765)

This passes in too many arguments to the diagnostic, I think OtherCtor should be removed.

125–126 ↗(On Diff #90569)

That would be handled automatically anyway (the select I suggested has an option for both). Also, you should have a test for the case involving the implicitly declared constructors.

leanil updated this revision to Diff 93112.Mar 27 2017, 5:05 AM

Make the warning message more precise.
Add tests for implicitly declared constructors.

Why does diff contain so many files?
Could you maybe merge the latest master into your branch?

leanil updated this revision to Diff 93114.Mar 27 2017, 5:17 AM

Correct earlier diff issue with outdated master.

leanil marked 2 inline comments as done.Mar 27 2017, 5:24 AM
leanil added inline comments.
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
119 ↗(On Diff #93114)

This is the most precise way to formulate the warning message I could come up with.
The condition for excluding either "copy" or "move" from the warning is to find only disabled instances of the constructor type, and there must be at least one, otherwise the compiler generated constructor (which is not present in this enumeration) can be hidden.

aaron.ballman added inline comments.Mar 29 2017, 11:45 AM
clang-tidy/misc/ForwardingReferenceOverloadCheck.cpp
119 ↗(On Diff #93114)

How about:

if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private)
  (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true;
else
  (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true;
136 ↗(On Diff #93114)

Can elide the braces.

140 ↗(On Diff #93114)

copy and the move -> copy and move

142 ↗(On Diff #93114)

The math is correct, but really makes me scratch my head to try to puzzle through it. Why not something along the lines of Copy && Move ? 2 : (Copy ? 0 : 1)

leanil updated this revision to Diff 93774.Apr 2 2017, 3:29 AM

Simplify checking the presence of copy and move ctors.

leanil marked 4 inline comments as done.Apr 2 2017, 3:30 AM
aaron.ballman accepted this revision.Apr 3 2017, 5:12 PM

Aside from some minor nits about newlines, LGTM!

docs/clang-tidy/checks/misc-forwarding-reference-overload.rst
51–52 ↗(On Diff #93774)

You can remove the empty lines at the end.

test/clang-tidy/misc-forwarding-reference-overload.cpp
140 ↗(On Diff #93774)

Please add a newline at the end of the file.

This revision is now accepted and ready to land.Apr 3 2017, 5:12 PM
leanil updated this revision to Diff 94028.Apr 4 2017, 3:07 AM

fix new lines

LGTM! If you need someone to commit on your behalf, let us know. I won't be able to do it this week. but I'm guessing @alexfh or someone else may be available to help (or I can get to it next week).

This revision was automatically updated to reflect the committed changes.
jbcoe added a subscriber: jbcoe.Apr 6 2017, 4:20 AM
dkrupp added a subscriber: dkrupp.Apr 11 2017, 6:59 AM