diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -14,6 +14,7 @@ DeleteNullPointerCheck.cpp DuplicateIncludeCheck.cpp ElseAfterReturnCheck.cpp + ForwardUsageCheck.cpp FunctionCognitiveComplexityCheck.cpp FunctionSizeCheck.cpp IdentifierLengthCheck.cpp diff --git a/clang-tools-extra/clang-tidy/readability/ForwardUsageCheck.h b/clang-tools-extra/clang-tidy/readability/ForwardUsageCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/ForwardUsageCheck.h @@ -0,0 +1,39 @@ +//===--- ForwardUsageCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FORWARDUSAGECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FORWARDUSAGECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// 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. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/forward-usage.html +class ForwardUsageCheck : public ClangTidyCheck { +public: + ForwardUsageCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; + +private: + const bool DisableTypeMismatchSuggestion; + const bool DisableRemoveSuggestion; + const bool DisableMoveSuggestion; + const bool IgnoreDependentExpresions; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_FORWARDUSAGECHECK_H diff --git a/clang-tools-extra/clang-tidy/readability/ForwardUsageCheck.cpp b/clang-tools-extra/clang-tidy/readability/ForwardUsageCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/ForwardUsageCheck.cpp @@ -0,0 +1,192 @@ +//===--- ForwardUsageCheck.cpp - clang-tidy ------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "ForwardUsageCheck.h" +#include "../utils/ASTUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +AST_MATCHER_P(UnresolvedLookupExpr, hasOnyTemplateArgumentLoc, + ast_matchers::internal::Matcher, + InnerMatcher) { + return Node.getNumTemplateArgs() == 1U && + InnerMatcher.matches(Node.getTemplateArgs()[0], Finder, Builder); +} + +AST_MATCHER(QualType, isFixedType) { + QualType CleanType = Node.getCanonicalType().getNonReferenceType(); + + if (CleanType->containsErrors()) + return false; + + if (CleanType->getDependence() == TypeDependenceScope::None) + return true; + + const auto *TemplateStruct = CleanType->getAs(); + if (TemplateStruct) { + return TemplateStruct->getTemplateName().getDependence() == + TemplateNameDependence::None; + } + + return false; +} + +inline SourceRange getAnglesLoc(const Expr &MatchedCallee) { + + if (const auto *DeclRefExprCallee = + llvm::dyn_cast_or_null(&MatchedCallee)) + return SourceRange(DeclRefExprCallee->getLAngleLoc(), + DeclRefExprCallee->getRAngleLoc()); + if (const auto *UnresolvedLookupExprCallee = + llvm::dyn_cast_or_null(&MatchedCallee)) + return SourceRange(UnresolvedLookupExprCallee->getLAngleLoc(), + UnresolvedLookupExprCallee->getRAngleLoc()); + return SourceRange(); +} + +} // namespace + +inline bool isDeducedType(const QualType &Type) { + return !Type->isSpecificBuiltinType(BuiltinType::Dependent); +} + +inline bool hasXValueArgument(const CallExpr &Call) { + return Call.getArg(0U)->isXValue(); +} + +ForwardUsageCheck::ForwardUsageCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + DisableTypeMismatchSuggestion( + Options.get("DisableTypeMismatchSuggestion", false)), + DisableRemoveSuggestion(Options.get("DisableRemoveSuggestion", false)), + DisableMoveSuggestion(Options.get("DisableMoveSuggestion", false)), + IgnoreDependentExpresions( + Options.get("IgnoreDependentExpresions", false)) {} + +void ForwardUsageCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "DisableTypeMismatchSuggestion", + DisableTypeMismatchSuggestion); + Options.store(Opts, "DisableRemoveSuggestion", DisableRemoveSuggestion); + Options.store(Opts, "DisableMoveSuggestion", DisableMoveSuggestion); + Options.store(Opts, "IgnoreDependentExpresions", IgnoreDependentExpresions); +} + +bool ForwardUsageCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return LangOpts.CPlusPlus11; +} + +void ForwardUsageCheck::registerMatchers(MatchFinder *Finder) { + const auto templateArgumentType = + templateArgumentLoc(hasTypeLoc(loc(qualType(isFixedType()).bind("T")))); + + Finder->addMatcher( + traverse( + TK_IgnoreUnlessSpelledInSource, + callExpr( + unless(isExpansionInSystemHeader()), argumentCountIs(1U), + IgnoreDependentExpresions + ? expr(unless(isInstantiationDependent())) + : expr(), + + unless(hasAncestor(functionDecl(isImplicit()))), + + callee( + expr(anyOf(declRefExpr(hasDeclaration(functionDecl( + hasName("::std::forward"))), + hasTemplateArgumentLoc( + 0U, templateArgumentLoc( + templateArgumentType))), + unresolvedLookupExpr( + hasAnyDeclaration( + namedDecl(hasName("::std::forward"))), + hasOnyTemplateArgumentLoc(templateArgumentLoc( + templateArgumentType))))) + .bind("callee")), + hasArgument(0U, expr(hasType(qualType().bind("arg"))))) + .bind("call")), + this); +} + +void ForwardUsageCheck::check(const MatchFinder::MatchResult &Result) { + const auto &MatchedCallee = *Result.Nodes.getNodeAs("callee"); + if (MatchedCallee.getBeginLoc().isMacroID() || + MatchedCallee.getEndLoc().isMacroID() || + !utils::rangeCanBeFixed(MatchedCallee.getSourceRange(), + Result.SourceManager)) + return; + + const auto &MatchedExpr = *Result.Nodes.getNodeAs("call"); + + const auto MatchedTemplateType = *Result.Nodes.getNodeAs("T"); + const auto MatchedArgumentType = *Result.Nodes.getNodeAs("arg"); + + if (!DisableTypeMismatchSuggestion && + MatchedTemplateType.getCanonicalType().getNonReferenceType() != + MatchedArgumentType.getCanonicalType().getNonReferenceType() && + isDeducedType(MatchedArgumentType)) { + + auto Diagnostic = + diag(MatchedExpr.getBeginLoc(), + "using 'std::forward' for type conversions from %0 to %1 is not " + "recommended here, use 'static_cast' instead"); + Diagnostic << MatchedArgumentType << MatchedTemplateType; + + const SourceRange AngleSourceRange = getAnglesLoc(MatchedCallee); + if (AngleSourceRange.isInvalid()) + return; + + if (!MatchedTemplateType.getCanonicalType()->isReferenceType()) { + Diagnostic << FixItHint::CreateReplacement( + SourceRange(AngleSourceRange.getEnd(), AngleSourceRange.getEnd()), + " &&>"); + } + + Diagnostic << FixItHint::CreateReplacement( + SourceRange(MatchedCallee.getBeginLoc(), AngleSourceRange.getBegin()), + "static_cast<"); + return; + } + + const bool LValueReferenceTemplateType = + MatchedTemplateType->isLValueReferenceType(); + const bool XValueArgument = + hasXValueArgument(MatchedExpr) && isDeducedType(MatchedArgumentType); + + if (!LValueReferenceTemplateType && !XValueArgument) { + if (DisableMoveSuggestion) + return; + + diag(MatchedExpr.getBeginLoc(), + "use 'std::move' instead of 'std::forward' here, as it more " + "accurately reflects the intended purpose") + << FixItHint::CreateReplacement(MatchedCallee.getSourceRange(), + "std::move"); + return; + } + + if (DisableRemoveSuggestion) + return; + + const SourceLocation BeforeArgBegin = + MatchedExpr.getArg(0U)->getBeginLoc().getLocWithOffset(-1); + diag(MatchedExpr.getBeginLoc(), "remove redundant use of 'std::forward' as " + "it is unnecessary in this context") + << FixItHint::CreateRemoval(SourceRange(MatchedExpr.getRParenLoc(), + MatchedExpr.getRParenLoc())) + << FixItHint::CreateRemoval( + SourceRange(MatchedExpr.getBeginLoc(), BeforeArgBegin)); +} + +} // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -19,6 +19,7 @@ #include "DeleteNullPointerCheck.h" #include "DuplicateIncludeCheck.h" #include "ElseAfterReturnCheck.h" +#include "ForwardUsageCheck.h" #include "FunctionCognitiveComplexityCheck.h" #include "FunctionSizeCheck.h" #include "IdentifierLengthCheck.h" @@ -78,6 +79,8 @@ "readability-duplicate-include"); CheckFactories.registerCheck( "readability-else-after-return"); + CheckFactories.registerCheck( + "readability-forward-usage"); CheckFactories.registerCheck( "readability-function-cognitive-complexity"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -116,6 +116,13 @@ Checks that all implicit and explicit inline functions in header files are tagged with the ``LIBC_INLINE`` macro. +- New :doc:`readability-forward-usage + ` check. + + 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. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -338,6 +338,7 @@ `readability-delete-null-pointer `_, "Yes" `readability-duplicate-include `_, "Yes" `readability-else-after-return `_, "Yes" + `readability-forward-usage `_, "Yes" `readability-function-cognitive-complexity `_, `readability-function-size `_, `readability-identifier-length `_, diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/forward-usage.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/forward-usage.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/forward-usage.rst @@ -0,0 +1,120 @@ +.. title:: clang-tidy - readability-forward-usage + +readability-forward-usage +========================= + +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. + +Recognized applications +----------------------- + +Check can detect different types of usages, as follows: + +Casting between different types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The purpose of ``std::forward`` is to forward the value category of an argument, +while preserving its constness and reference qualifiers. It is not intended to +be used for type conversions, as that is outside the scope of its intended use. + +Using ``std::forward`` to convert between two types is a misuse because it can +result in unexpected behavior, such as object slicing. Both the input and output +types should be cv-equivalent, meaning that they represent the same fundamental +type after removing all ``const``, ``volatile``, and reference qualifiers. + +Check will suggest usage of ``static_cast`` in this situation. + +.. code-block:: c++ + + struct Base {}; + struct Derived : Base {}; + + void func(Base& base) { + doSomething(std::forward(base)); // Incorrect usage + doSomething(static_cast(base)); // Suggested usage + } + + +Misusing ``std::forward`` as ``std::move`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While it is technically possible to use ``std::forward`` to convert an `lvalue` +to an `rvalue` when the resulting type is fixed, this is a misapplication +because it can result in code that is difficult to read and understand. + +The C++ standard provides a better alternative in ``std::move``, which is +intended specifically for converting an `lvalue` to an `rvalue`, and clearly +communicates the intention of the developer. + +Check will suggest usage of ``std::move`` in this situation. + +.. code-block:: c++ + + struct Object {}; + + void func(Object& obj) { + doSomething(std::forward(obj)); // Incorrect usage + doSomething(std::move(obj)); // Suggested usage + } + + +Redundant use of ``std::forward`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When using ``std::forward`` to convert from an `lvalue` to another `lvalue`, +or from an `rvalue` to another `rvalue`, the usage is redundant and should be +avoided. In such cases, the ``std::forward`` call is unnecessary since the +types are already equivalent, and it does not provide any additional value in +preserving the value category of the argument. + +It is generally recommended to simply remove the ``std::forward`` call in +these cases, as it does not have any effect on the result of the expression. + +Check will suggest removal of ``std::forward`` in this situation. + +.. code-block:: c++ + + struct Object {}; + + Object createObject(); + + void func(Object&& obj) { + doSomething(std::forward(createObject())); // Incorrect usage + doSomething(std::forward(createObject())); // Incorrect usage + doSomething(createObject()); // Suggested usage + } + + void func(Object& obj) { + doSomething(std::forward(obj)); // Incorrect usage + doSomething(obj); // Suggested usage + } + + +Options +------- + +.. option:: DisableTypeMismatchSuggestion + + Disables suggestions for type mismatch situations. When this option is + enabled, the check treats situations with different types as if they were + cv-equal and won't suggest using ``static_cast``, but will suggest using + ``std::move`` or removing the call. + This option has a default value of `false`. + +.. option:: DisableRemoveSuggestion + + Disables suggestions to remove redundant ``std::forward`` calls. + This option has a default value of `false`. + +.. option:: DisableMoveSuggestion + + Disables suggestions to use ``std::move`` instead of ``std::forward`` for + `lvalue` to `rvalue` conversions. + This option has a default value of `false`. + +.. option:: IgnoreDependentExpresions + + Ignore dependent expressions during analysis (like any templates dependent + code). This option has a default value of `false`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/forward-usage.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/forward-usage.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/forward-usage.cpp @@ -0,0 +1,257 @@ +// RUN: %check_clang_tidy -std=c++11-or-later %s readability-forward-usage %t + +namespace std { + +struct false_type { + static constexpr bool value = false; +}; + +struct true_type { + static constexpr bool value = true; +}; + +template +struct is_lvalue_reference : false_type {}; + +template +struct is_lvalue_reference : true_type {}; + +template +struct remove_reference { + using type = T; +}; + +template +struct remove_reference { + using type = T; +}; + +template +struct remove_reference { + using type = T; +}; + +template +using remove_reference_t = typename remove_reference::type; + +template +constexpr T&& forward(typename std::remove_reference::type& t) noexcept { + return static_cast(t); +} + +template +constexpr T&& forward(typename std::remove_reference::type&& t) noexcept { + static_assert(!std::is_lvalue_reference::value, "Can't forward an rvalue as an lvalue."); + return static_cast(t); +} + +template +constexpr typename std::remove_reference::type&& move(T&& t) noexcept { + return static_cast::type&&>(t); +} + +} + +struct TestType { + int value = 0U; +}; + +struct TestTypeEx : TestType { +}; + +template +void test(Args&&...) {} + + +template +void testCorrectForward(T&& value) { + test(std::forward(value)); +} + +template +void testForwardVariadicTemplate(Args&& ...args) { + test(std::forward(args)...); +} + +void callAll() { + TestType type1; + const TestType type2; + testCorrectForward(type1); + testCorrectForward(type2); + testCorrectForward(std::move(type1)); + testCorrectForward(std::move(type2)); + testForwardVariadicTemplate(type1, TestType(), std::move(type2)); +} + +void testExplicit(TestType& value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} +} + +void testExplicit2(const TestType& value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} +} + +void testExplicit3(TestType value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} +} + +void testExplicit4(TestType&& value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} +} + +void testExplicit5(const TestType&& value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} +} + +void testExplicit6(TestType&& value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: remove redundant use of 'std::forward' as it is unnecessary in this context [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(value);{{$}} +} + +void testExplicit7(TestType value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: remove redundant use of 'std::forward' as it is unnecessary in this context [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(value);{{$}} +} + +void testExplicit8(TestType value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} +} + +void testExplicit9(TestTypeEx value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: using 'std::forward' for type conversions from 'TestTypeEx' to 'TestType' is not recommended here, use 'static_cast' instead [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(static_cast(value));{{$}} +} + +void testExplicit10(TestTypeEx value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: using 'std::forward' for type conversions from 'TestTypeEx' to 'TestType &' is not recommended here, use 'static_cast' instead [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(static_cast(value));{{$}} +} + +void testExplicit11(TestTypeEx value) { + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: using 'std::forward' for type conversions from 'TestTypeEx' to 'TestType &&' is not recommended here, use 'static_cast' instead [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(static_cast(value));{{$}} +} + +void testExplicit12(TestType value) { + test(std::forward(std::move(value))); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: remove redundant use of 'std::forward' as it is unnecessary in this context [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} +} + +void testExplicit13() { + test(std::forward(TestType())); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: remove redundant use of 'std::forward' as it is unnecessary in this context [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(TestType());{{$}} +} + +TestType getTestType(); + +void testExplicit14() { + test(std::forward(getTestType())); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: remove redundant use of 'std::forward' as it is unnecessary in this context [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(getTestType());{{$}} +} + +#define FORWARD(x,y) std::forward(y) +#define FORWARD_FUNC std::forward +#define TEMPLATE_ARG(x) + +void testMacro(TestType value) { + test(FORWARD(TestType, value)); + test(FORWARD_FUNC(value)); + test(std::forward TEMPLATE_ARG(TestType)(value)); +} + +template +struct Wrapper {}; + +template +struct WrapperEx : Wrapper {}; + +template +struct TemplateClass +{ + void testTemplate(Wrapper value) { + test(std::forward>(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} + } +}; + +template +void testPartialTemplate(Wrapper value) { + test(std::forward>(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} + + using Type = Wrapper; + test(std::forward(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: use 'std::move' instead of 'std::forward' here, as it more accurately reflects the intended purpose [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(std::move(value));{{$}} + +} + +template +void testPartialTemplate(WrapperEx value) { + test(std::forward>(value)); +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: using 'std::forward' for type conversions from 'WrapperEx' to 'Wrapper' is not recommended here, use 'static_cast' instead [readability-forward-usage] +// CHECK-FIXES: {{^ }}test(static_cast &&>(value));{{$}} +} + +void testWrapper() { + TemplateClass().testTemplate(Wrapper()); + testPartialTemplate(Wrapper()); +} + +template