This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] Add readability-forward-usage check
Changes PlannedPublic

Authored by PiotrZSL on Feb 19 2023, 5:22 AM.



Suggests removing or replacing std::forward with std::move or
static_cast in cases where the template argument type is invariant and
the call always produces the same result.

Diff Detail

Event Timeline

PiotrZSL created this revision.Feb 19 2023, 5:22 AM
Herald added a project: Restricted Project. · View Herald Transcript
PiotrZSL updated this revision to Diff 498666.Feb 19 2023, 5:25 AM

Fix list.rst

Eugene.Zelenko added inline comments.

Excessive newline.


return {}; is shorter.


Usually pointers are used. Same in other similar places..


Excessive indentation. Same for other options below.

PiotrZSL marked 4 inline comments as done.EditedFeb 19 2023, 10:10 AM

Does anyone know why on WINDOWS generated AST is different:

LINUX (when function is never called):
|-FunctionTemplateDecl 0x5577338b2580 <line:211:1, line:216:1> line:212:6 testPartialTemplate
| |-TemplateTypeParmDecl 0x5577338b2288 <line:211:10, col:19> col:19 referenced typename depth 0 index 0 T
| `-FunctionDecl 0x5577338b24d8 <line:212:1, line:216:1> line:212:6 testPartialTemplate 'void (WrapperEx<T>)'
|   |-ParmVarDecl 0x5577338b23e0 <col:26, col:39> col:39 referenced value 'WrapperEx<T>':'WrapperEx<T>'
|   `-CompoundStmt 0x5577338b28a0 <col:46, line:216:1>
|     `-CallExpr 0x5577338b2878 <line:213:5, col:41> '<dependent type>'
|       |-UnresolvedLookupExpr 0x5577338b2668 <col:5> '<overloaded function type>' lvalue (ADL) = 'test' 0x557733898c10
|       `-CallExpr 0x5577338b2850 <col:10, col:40> '<dependent type>'
|         |-UnresolvedLookupExpr 0x5577338b27b0 <col:10, col:33> '<dependent type>' lvalue (no ADL) = 'forward' 0x5577338961d8 0x557733896818
|         `-DeclRefExpr 0x5577338b2830 <col:35> 'WrapperEx<T>':'WrapperEx<T>' lvalue ParmVar 0x5577338b23e0 'value' 'WrapperEx<T>':'WrapperEx<T>'

LINUX (when function is called)
|-FunctionTemplateDecl 0x556b33e8a5b0 <line:211:1, line:216:1> line:212:6 testPartialTemplate
| |-TemplateTypeParmDecl 0x556b33e8a2b8 <line:211:10, col:19> col:19 referenced typename depth 0 index 0 T
| |-FunctionDecl 0x556b33e8a508 <line:212:1, line:216:1> line:212:6 testPartialTemplate 'void (WrapperEx<T>)'
| | |-ParmVarDecl 0x556b33e8a410 <col:26, col:39> col:39 referenced value 'WrapperEx<T>':'WrapperEx<T>'
| | `-CompoundStmt 0x556b33e8a8d0 <col:46, line:216:1>
| |   `-CallExpr 0x556b33e8a8a8 <line:213:5, col:41> '<dependent type>'
| |     |-UnresolvedLookupExpr 0x556b33e8a698 <col:5> '<overloaded function type>' lvalue (ADL) = 'test' 0x556b33e70c40
| |     `-CallExpr 0x556b33e8a880 <col:10, col:40> '<dependent type>'
| |       |-UnresolvedLookupExpr 0x556b33e8a7e0 <col:10, col:33> '<dependent type>' lvalue (no ADL) = 'forward' 0x556b33e6e208 0x556b33e6e848
| |       `-DeclRefExpr 0x556b33e8a860 <col:35> 'WrapperEx<T>':'WrapperEx<T>' lvalue ParmVar 0x556b33e8a410 'value' 'WrapperEx<T>':'WrapperEx<T>'
| `-FunctionDecl 0x556b33e8e888 <line:212:1, line:216:1> line:212:6 used testPartialTemplate 'void (WrapperEx<TestType>)'

WINDOWS (when function is never called):
|-FunctionTemplateDecl 0x1ac13659de0 <line:211:1, line:212:44> col:6 testPartialTemplate
| |-TemplateTypeParmDecl 0x1ac13652238 <line:211:10, col:19> col:19 referenced typename depth 0 index 0 T
| `-FunctionDecl 0x1ac13659d38 <line:212:1, col:44> col:6 testPartialTemplate 'void (WrapperEx<T>)'
|   |-ParmVarDecl 0x1ac13652390 <col:26, col:39> col:39 value 'WrapperEx<T>':'WrapperEx<T>'
|   `-<<<NULL>>>

WINDOWS (when function is called):
|-FunctionTemplateDecl 0x1fe6f1b99a0 <line:211:1, line:216:1> line:212:6 testPartialTemplate
| |-TemplateTypeParmDecl 0x1fe6f1b9568 <line:211:10, col:19> col:19 referenced typename depth 0 index 0 T
| |-FunctionDecl 0x1fe6f1b98f8 <line:212:1, line:216:1> line:212:6 testPartialTemplate 'void (WrapperEx<T>)'
| | |-ParmVarDecl 0x1fe6f1b96c0 <col:26, col:39> col:39 referenced value 'WrapperEx<T>':'WrapperEx<T>'
| | `-CompoundStmt 0x1fe6f1c5070 <col:46, line:216:1>
| |   `-CallExpr 0x1fe6f1c5048 <line:213:5, col:41> '<dependent type>'
| |     |-UnresolvedLookupExpr 0x1fe6f1c4e40 <col:5> '<overloaded function type>' lvalue (ADL) = 'test' 0x1fe6f18df70
| |     `-CallExpr 0x1fe6f1c5020 <col:10, col:40> '<dependent type>'
| |       |-UnresolvedLookupExpr 0x1fe6f1c4f80 <col:10, col:33> '<dependent type>' lvalue (no ADL) = 'forward' 0x1fe6f18c3e8 0x1fe6f18ca28
| |       `-DeclRefExpr 0x1fe6f1c5000 <col:35> 'WrapperEx<T>':'WrapperEx<T>' lvalue ParmVar 0x1fe6f1b96c0 'value' 'WrapperEx<T>':'WrapperEx<T>'
| `-FunctionDecl 0x1fe6f1bebd8 <line:212:1, line:216:1> line:212:6 used testPartialTemplate 'void (WrapperEx<TestType>)'
|   |-TemplateArgument type 'TestType'

EDIT: Ok, found that -fno-delayed-template-parsing is an workaround for this (but still strange).

PiotrZSL updated this revision to Diff 498687.Feb 19 2023, 10:18 AM

Fixed review comments and added -fno-delayed-template-parsing

PiotrZSL published this revision for review.Feb 19 2023, 11:01 AM

Ready for review

Herald added a project: Restricted Project. · View Herald TranscriptFeb 19 2023, 11:01 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
PiotrZSL updated this revision to Diff 503092.Mar 7 2023, 10:38 AM

Ping, Rebase

PiotrZSL updated this revision to Diff 503297.Mar 8 2023, 3:22 AM

Rebase due to broken baseline

PiotrZSL updated this revision to Diff 503357.Mar 8 2023, 7:04 AM

Rebase due to broken baseline

Is it worth adding a cppcoreguidelines alias (ES.56)?


Curious what others thing but I think the tool should by default not remove or replace forward with static_cast. When an author has written forward, there is a good chance they intended to forward something (e.g., they forgot to use && in the parameter declaration). My hypothesis is that it's more likely the author intended to forward something, rather than than they were trying to use it as a cast (but had not intention of forward - why else would they have typed it out?). In this case, I think it merits manual review by default (so the tool should always warn, but without the fixits by default).

Is it worth adding a cppcoreguidelines alias (ES.56)?

Yes, you can pull this change (arc patch D144347 --nobranch) and create change that would depend on this one, and would register alias, also there you could override DisableRemoveSuggestion.
You can also help reviewing this check, to push it forward, point missing tests, scenarios. We could merge tests...


No, in this case if they used forward, they used this with different type, not adding & is not sufficient to trigger this case.

In theory we could, create alias bugprone-std-forward that would redirect to this check, but would set DisableTypeMismatchSuggestion to false, and would set DisableRemoveSuggestion & DisableMoveSuggestion to true.

In such case we could have readability check that would just suggest remove of std::forward or usage of std::move, and bugprone check that would only warn of casts between different types.

LegalizeAdulthood resigned from this revision.Mar 29 2023, 8:05 AM
ccotter accepted this revision.Mar 31 2023, 2:16 PM
ccotter added inline comments.

Curious, why have isExpansionInSystemHeader here, rather than rely on the system-headers command line option. Is that a matter of performance (this tool is not likely to apply for system headers)?


Makes sense to me. Happy to work on that once this is merged.

This revision is now accepted and ready to land.Mar 31 2023, 2:16 PM
PiotrZSL marked an inline comment as done.Mar 31 2023, 2:35 PM

I will leave this review open for 1 more week, in case someone have some comments, and by someone I mean you @carlosgalvezp.


isExpansionInSystemHeader works on check level, when --system-headers on diagnostic level. This mean that check will be executed, and warning will be excluded during call to diag.
For some checks this can be expensive.
Read more here:

ccotter added inline comments.Mar 31 2023, 6:42 PM

I think we might need more tests when IgnoreDependentExpresions is true. When I was playing around and hardcoded IgnoreDependentExpresions to false on line 99, the tests still pass.


Would you see use in expanding use of this check to cases that (broadly, cppcoreguiideline ES.56) apply forward when the argument is not a forwarding reference? Or separate check (as the one I proposed in Specifically, when the template parameter is deduced:

template <class T> void forwards_incorrectly(T& t) { T other = std::forward<T>(t); } // ignored by readability-forward-usage
PiotrZSL planned changes to this revision.Apr 1 2023, 1:42 AM

TODO: Extract DisableTypeMismatchSuggestion & that logic into separate check in bugprone category.
TODO: Validate more situations with templates.

njames93 requested changes to this revision.Apr 10 2023, 6:29 AM
njames93 added inline comments.

Use static instead of inline. Inline should only be used for functions(or variables) defined in header files.
Same goes for the other inline functions.
Also this function should be moved outside the anonymous namespace.


Remove the llvm:: qualifier and the null check is redundant here as its guaranteed to never be called with a non-null argument.




Rather than calling traverse, the canoncial way to handle this is by overriding the getCheckTraversalKind virtual function to return TK_IgnoreUnlessSpelledInSource


This needs addressing before this can be landed


It's a good idea to follow to DRY principle:

auto IsNamedStdForward = hasName("::std::forward");

Then you can use that here and below.

A similar point can be made with the bind IDs, if you define the IDs as a static constexpr char [] you can reuse them during`bind` and getNodeAs calls, helps avoid bugs if the check is ever upgraded.

@PiotrZSL - checking back, do you plan to revisit this change soon (I think there are some pending feedback and you planned some changes)? I'd like to see this in change merged in. Let me know if I can help.

@PiotrZSL - checking back, do you plan to revisit this change soon (I think there are some pending feedback and you planned some changes)? I'd like to see this in change merged in. Let me know if I can help.

I will try to re-visit this change this week. I need to split this check somehow. Probably extract casting between types into separate bugprone check and add some better support for template types.
Give me 2-3 days.

Great, thanks!