diff --git a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt @@ -18,6 +18,7 @@ PerformanceTidyModule.cpp TriviallyDestructibleCheck.cpp TypePromotionInMathFnCheck.cpp + UnnecessaryCopyOnLastUseCheck.cpp UnnecessaryCopyInitialization.cpp UnnecessaryValueParamCheck.cpp diff --git a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp --- a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp @@ -23,6 +23,7 @@ #include "TriviallyDestructibleCheck.h" #include "TypePromotionInMathFnCheck.h" #include "UnnecessaryCopyInitialization.h" +#include "UnnecessaryCopyOnLastUseCheck.h" #include "UnnecessaryValueParamCheck.h" namespace clang { @@ -59,6 +60,8 @@ "performance-type-promotion-in-math-fn"); CheckFactories.registerCheck( "performance-unnecessary-copy-initialization"); + CheckFactories.registerCheck( + "performance-unnecessary-copy-on-last-use"); CheckFactories.registerCheck( "performance-unnecessary-value-param"); } diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyOnLastUseCheck.h b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyOnLastUseCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyOnLastUseCheck.h @@ -0,0 +1,62 @@ +//===--- UnnecessaryCopyOnLastUseCheck.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_PERFORMANCE_UNNECESSARY_COPY_ON_LAST_USE_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_ON_LAST_USE_H + +#include "../ClangTidyCheck.h" +#include "../utils/IncludeInserter.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { + +class CFG; +namespace tidy::performance { + +/// A check that flags value parameters on last usages that can safely be moved, +/// because they are copied anyway. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/performance/unnecessary-copy-on-last-use.html + +class UnnecessaryCopyOnLastUseCheck : public ClangTidyCheck { +public: + UnnecessaryCopyOnLastUseCheck(StringRef Name, ClangTidyContext *Context); + UnnecessaryCopyOnLastUseCheck(UnnecessaryCopyOnLastUseCheck &&) = delete; + UnnecessaryCopyOnLastUseCheck(const UnnecessaryCopyOnLastUseCheck &) = delete; + UnnecessaryCopyOnLastUseCheck & + operator=(UnnecessaryCopyOnLastUseCheck &&) = delete; + UnnecessaryCopyOnLastUseCheck & + operator=(const UnnecessaryCopyOnLastUseCheck &) = delete; + ~UnnecessaryCopyOnLastUseCheck() override; + + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, + Preprocessor *ModuleExpanderPP) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void onEndOfTranslationUnit() override; + +private: + CFG *getOrCreateCFG(FunctionDecl const *FD, ASTContext *C); + + utils::IncludeInserter Inserter; + std::vector const BlockedTypes; + std::vector const BlockedFunctions; + + // cfg cache + llvm::DenseMap> CFGs; +}; + +} // namespace tidy::performance +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_UNNECESSARY_COPY_ON_LAST_USE_H diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyOnLastUseCheck.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyOnLastUseCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyOnLastUseCheck.cpp @@ -0,0 +1,310 @@ +//===--- UnnecessaryCopyOnLastUseCheck.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 "UnnecessaryCopyOnLastUseCheck.h" + +#include "../utils/DeclRefExprUtils.h" +#include "../utils/FixItHintUtils.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "../utils/TypeTraits.h" +#include "FasterStringFindCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ASTTypeTraits.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Analysis/CFG.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/ADT/PriorityQueue.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include +#include +#include +#include + +using namespace clang; +using namespace clang::tidy; +using namespace clang::tidy::performance; +using namespace clang::ast_matchers; + +static constexpr auto BlockedTypesOption = "BlockedTypes"; +static constexpr auto BlockedFunctionsOption = "BlockedFunctions"; + +namespace { + +struct FindDeclRefBlockReturn { + CFGBlock const *DeclRefBlock = nullptr; + CFGBlock::const_iterator StartElement{}; +}; + +enum class Usage { Usage, DefiniteLastUse, LikelyDefiniteLastUse }; + +} // namespace + +static auto findDeclRefBlock(CFG const *TheCFG, DeclRefExpr const *DeclRef) + -> FindDeclRefBlockReturn { + for (CFGBlock *Block : *TheCFG) { + auto Iter = std::find_if( + Block->Elements.begin(), Block->Elements.end(), + [&, DeclRef](CFGElement const &Element) { + if (Element.getKind() == CFGElement::Statement) { + return Element.template castAs().getStmt() == DeclRef; + } + return false; + }); + if (Iter != Block->Elements.end()) { + return {Block, ++Iter}; + } + } + return {nullptr, {}}; +} + +static auto +nextUsageInCurrentBlock(FindDeclRefBlockReturn const &StartBlockElement, + DeclRefExpr const *DeclRef) { + // Search for uses in the current block + auto Begi = StartBlockElement.StartElement; + auto Endi = StartBlockElement.DeclRefBlock->Elements.end(); + auto Iter = std::find_if(Begi, Endi, [&](CFGElement const &Element) { + if (Element.getKind() == CFGElement::Statement) { + if (auto *Stmt = Element.template castAs().getStmt()) { + if (auto *DRE = dyn_cast(Stmt)) { + if (DRE->getDecl() == DeclRef->getDecl()) { + return true; + } + } + } + } + return false; + }); + return Iter != Endi ? &*Iter : nullptr; +} + +static bool isLHSOfAssignment(DeclRefExpr const *DeclRef, ASTContext &Context) { + auto Matches = match( + stmt(cxxOperatorCallExpr( + isAssignmentOperator(), + hasLHS(ignoringParenImpCasts(declRefExpr(equalsNode(DeclRef)))))), + Context); + return !Matches.empty(); +} + +static Usage definiteLastUse(ASTContext *Context, CFG *const TheCFG, + DeclRefExpr const *DeclRef) { + assert(TheCFG != nullptr); + + // Find the CFGBlock containing the DeclRefExpr + auto StartBlockElement = findDeclRefBlock(TheCFG, DeclRef); + assert(StartBlockElement.DeclRefBlock != nullptr); + + // Find next uses of the DeclRefExpr + + auto TraverseCFGForUsage = [&]() -> Usage { + llvm::SmallPtrSet VisitedBlocks; + llvm::SmallVector Worklist; + + auto HandleInternal = [&](FindDeclRefBlockReturn const &BlockElement) { + CFGElement const *NextUsageE = + nextUsageInCurrentBlock(BlockElement, DeclRef); + if (NextUsageE) { + if (bool const IsLastUsage = + isLHSOfAssignment(llvm::cast( + NextUsageE->castAs().getStmt()), + *Context); + !IsLastUsage) { + return Usage::Usage; + } + return Usage::DefiniteLastUse; + } + assert(BlockElement.DeclRefBlock); + // No successing DeclRefExpr found, appending successors + for (CFGBlock const *Succ : BlockElement.DeclRefBlock->succs()) { + if (Succ) { // Succ can be nullptr, if a block is unreachable + Worklist.push_back(Succ); + } + } + return Usage::DefiniteLastUse; // No usage found, assume last use + }; + + if (auto FoundUsage = HandleInternal(StartBlockElement); + FoundUsage == Usage::Usage) { // Usage found + return FoundUsage; + } + while (!Worklist.empty()) { + CFGBlock const *Block = Worklist.pop_back_val(); + if (!VisitedBlocks.insert(Block).second) { + continue; + } + if (auto FoundUsage = HandleInternal({Block, Block->Elements.begin()}); + FoundUsage == Usage::Usage) { + return FoundUsage; + } + } + return Usage::DefiniteLastUse; + }; + + return TraverseCFGForUsage(); +} + +namespace clang::tidy::performance { + +UnnecessaryCopyOnLastUseCheck::~UnnecessaryCopyOnLastUseCheck() = default; + +UnnecessaryCopyOnLastUseCheck::UnnecessaryCopyOnLastUseCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Inserter(Options.getLocalOrGlobal("IncludeStyle", + utils::IncludeSorter::IS_LLVM), + areDiagsSelfContained()), + BlockedTypes( + utils::options::parseStringList(Options.get(BlockedTypesOption, ""))), + BlockedFunctions(utils::options::parseStringList( + Options.get(BlockedFunctionsOption, ""))), + CFGs() {} + +void UnnecessaryCopyOnLastUseCheck::registerMatchers(MatchFinder *Finder) { + auto const ValueParameter = + declRefExpr(hasType(qualType(unless(anyOf( + isConstQualified(), referenceType(), pointerType(), + hasDeclaration(namedDecl( + matchers::matchesAnyListedName(BlockedTypes))) // + ))))) + .bind("param"); + + auto const IsMoveAssingable = cxxOperatorCallExpr( + hasDeclaration(cxxMethodDecl( + isCopyAssignmentOperator(), + ofClass(hasMethod(cxxMethodDecl(isMoveAssignmentOperator(), + unless(isDeleted())))))), + hasRHS(ValueParameter)); + + auto const IsMoveConstructible = + ignoringElidableConstructorCall(ignoringParenImpCasts( + cxxConstructExpr( + unless(hasParent(callExpr(hasDeclaration(namedDecl( + matchers::matchesAnyListedName(BlockedFunctions)))))), + hasDeclaration(cxxConstructorDecl( + isCopyConstructor(), + ofClass(hasMethod(cxxConstructorDecl(isMoveConstructor(), + unless(isDeleted())))))), + hasArgument(0, ValueParameter)) + .bind("constructExpr"))); + + Finder->addMatcher(stmt(anyOf(IsMoveAssingable, expr(IsMoveConstructible))), + this); +} + +void UnnecessaryCopyOnLastUseCheck::check( + const MatchFinder::MatchResult &Result) { + TraversalKindScope const RAII(*Result.Context, + TK_IgnoreUnlessSpelledInSource); + + const auto *Param = Result.Nodes.getNodeAs("param"); + // const CXXConstructExpr *ConstructExpr = + // Result.Nodes.getNodeAs("constructExpr"); + const ValueDecl *const DeclOfParam = Param->getDecl(); + const DeclContext *const FunctionOfDeclContext = + DeclOfParam->getParentFunctionOrMethod(); + + if (Param->getType().isTriviallyCopyableType(*Result.Context)) { + return; // A move would decrease readability without any benefit + } + + if (!FunctionOfDeclContext) { + // The parameter is not defined in a function, therefore it is not + // possible to check if it is the last use via CFG analysis + // Todo (improvement): Add a flag to show unanalyzable cases + return; + } + + const auto *const FunctionOfDecl = + llvm::cast(FunctionOfDeclContext); + + const auto *const VarDeclVal = llvm::dyn_cast(DeclOfParam); + if (!VarDeclVal) { + return; + } + + Usage DefiniteLastUse = definiteLastUse( + Result.Context, getOrCreateCFG(FunctionOfDecl, Result.Context), Param); + + if (DefiniteLastUse == Usage::Usage) { + return; + } + + // Template code cant be fixed currently + if (!FunctionOfDecl->isTemplateInstantiation()) { + clang::SourceManager &SM = *Result.SourceManager; + SourceLocation EndLoc = Lexer::getLocForEndOfToken( + Param->getLocation(), 0, SM, Result.Context->getLangOpts()); + auto Diag = + diag( + Param->getExprLoc(), + "Parameter '%0' is copied on last use, consider moving it instead.") + << Param->getDecl()->getNameAsString() + << FixItHint::CreateInsertion(Param->getBeginLoc(), "std::move(") + << FixItHint::CreateInsertion(EndLoc, ")") + << Inserter.createIncludeInsertion(SM.getFileID(Param->getBeginLoc()), + ""); + } else { // Template code can't be fixed currently, also a std::forward may be + // more appropriate + auto Diag = + diag(Param->getExprLoc(), "Parameter '%0' may be copied on last use." + "consider forwarding it instead.") + << Param->getDecl()->getNameAsString(); + } +} + +void UnnecessaryCopyOnLastUseCheck::registerPPCallbacks( + const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { + Inserter.registerPreprocessor(PP); +} + +void UnnecessaryCopyOnLastUseCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "IncludeStyle", Inserter.getStyle()); + Options.store(Opts, BlockedTypesOption, + utils::options::serializeStringList(BlockedTypes)); + Options.store(Opts, BlockedFunctionsOption, + utils::options::serializeStringList(BlockedFunctions)); +} + +void UnnecessaryCopyOnLastUseCheck::onEndOfTranslationUnit() {} + +static auto createBuildOptions() { + CFG::BuildOptions Options; + Options.setAlwaysAdd(DeclRefExpr::DeclRefExprClass); + Options.AddImplicitDtors = true; + Options.AddTemporaryDtors = true; + return Options; +} + +CFG *UnnecessaryCopyOnLastUseCheck::getOrCreateCFG(FunctionDecl const *FD, + ASTContext *C) { + static auto BO = createBuildOptions(); + + if (auto Iter = this->CFGs.find(FD); Iter != this->CFGs.end()) { + return Iter->second.get(); + } + + auto EmplaceResult = + this->CFGs.try_emplace(FD, CFG::buildCFG(nullptr, FD->getBody(), C, BO)); + return EmplaceResult.first->second.get(); +} +} // namespace clang::tidy::performance 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 @@ -115,6 +115,13 @@ Warns when using ``do-while`` loops. +- New :doc:`performance-unnecessary-copy-on-last-use + ` check. + + Finds variables of non-trivially-copyable types, that are used in a copy + construction on their last usage and suggest to wrap the usage in a + ``std::move``. + 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 @@ -318,6 +318,7 @@ `performance-trivially-destructible `_, "Yes" `performance-type-promotion-in-math-fn `_, "Yes" `performance-unnecessary-copy-initialization `_, "Yes" + `performance-unnecessary-copy-on-last-use `_, "Yes" `performance-unnecessary-value-param `_, "Yes" `portability-restrict-system-includes `_, "Yes" `portability-simd-intrinsics `_, diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/unnecessary-copy-on-last-use.rst b/clang-tools-extra/docs/clang-tidy/checks/performance/unnecessary-copy-on-last-use.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/performance/unnecessary-copy-on-last-use.rst @@ -0,0 +1,72 @@ +.. title:: clang-tidy - performance-unnecessary-copy-on-last-use + +performance-unnecessary-copy-on-last-use +======================================== + +Finds variables of non-trivially-copyable types, that are used in a copy +construction on their last usage and suggest to wrap the usage in a +``std::move``. The usage just before an assignment is interpreted as last usage. + +Many programmers tend to forget ``std::move`` for variables that can be moved. +Instead, the compiler creates implicit copies, which can significantly decrease +performance. + +Example +-------- + +.. code-block:: c++ + + void acceptor(std::vector v); + void Function() { + std::vector v; + v.emplace_back(20); + // The warning will suggest passing this as a rvalue-reference + acceptor(v); + } + +Options +------- + +.. option:: BlockedTypes + + A semicolon-separated list of names of types allowed to be copied on last + usage. Regular expressions are accepted, e.g. `[Rr]ef(erence)?$` matches + every type with suffix `Ref`, `ref`, `Reference` and `reference`. + The default is empty. If a name in the list contains the sequence `::` it + is matched against the qualified typename (i.e. `namespace::Type`, + otherwise it is matched against only the type name (i.e. `Type`). + +.. option:: BlockedFunctions + A semicolon-separated list of names of functions who's parameters do not + participate in the resolution. + Regular expressions are accepted, e.g. `[Rr]ef(erence)?$` matches + every type with suffix `Ref`, `ref`, `Reference` and `reference`. + The default is empty. If a name in the list contains the sequence `::` it + is matched against the qualified typename (i.e. `namespace::Type`, + otherwise it is matched against only the type name (i.e. `Type`). + +.. option:: IncludeStyle + + A string specifying which include-style is used, `llvm` or `google`. Default + is `llvm`. + +Limitations +----------- + +This check does not implement a heuristic, if a variable is used as guard until +the end of it's scope. It also does not check, if a variable has any side +effects in the destructor, which must be applied at the end of the scope. +Therefore, it will suggest to use ``std::move`` in the +following case, where `guard` protects `i`: + +.. code-block:: cpp + + void acceptor(std::shared_ptr>, int& i); + + void f(std::shared_ptr> guard, int& i) { + acceptor(guard, i); + i++; + } + + This check is also unable to detect last usages for fields, if the parent + class is destructed after the last usage. \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-copy-on-last-use.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-copy-on-last-use.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/performance/unnecessary-copy-on-last-use.cpp @@ -0,0 +1,104 @@ +// RUN: %check_clang_tidy %s -std=c++17 performance-unnecessary-copy-on-last-use %t +// RUN: %check_clang_tidy %s -std=c++11 performance-unnecessary-copy-on-last-use %t +// CHECK-FIXES: #include + +struct Movable { + Movable() = default; + Movable(Movable const &) = default; + Movable(Movable &&) = default; + Movable &operator=(Movable const &) = default; + Movable &operator=(Movable &&) = default; + ~Movable(); + + void memberUse() {} +}; +// static_assert(!std::is_trivially_copyable_v, "Movable must not be trivially copyable"); + +void valueReceiver(Movable Mov); +void constRefReceiver(Movable const &Mov); + +void valueTester() { + Movable Mov{}; + valueReceiver(Mov); + valueReceiver(Mov); + // CHECK-MESSAGES: [[@LINE-1]]:17: warning: Parameter 'Mov' is copied on last use, consider moving it instead. [performance-unnecessary-copy-on-last-use] + // CHECK-FIXES: valueReceiver(std::move(Mov)); + Mov = Movable{}; + valueReceiver(Mov); + // CHECK-MESSAGES: [[@LINE-1]]:17: warning: Parameter 'Mov' is copied on last use, consider moving it instead. [performance-unnecessary-copy-on-last-use] + // CHECK-FIXES: valueReceiver(std::move(Mov)); +} + +void testUsageInBranch(bool Splitter) { + Movable Mov{}; + + valueReceiver(Mov); + if(Splitter){ + Mov.memberUse(); + } else { + Mov = Movable{}; + } + valueReceiver(Mov); + // CHECK-MESSAGES: [[@LINE-1]]:17: warning: Parameter 'Mov' is copied on last use, consider moving it instead. [performance-unnecessary-copy-on-last-use] + // CHECK-FIXES: valueReceiver(std::move(Mov)); + + if(Splitter){ + Mov = Movable{}; + } else { + Mov = Movable{}; + } + valueReceiver(Mov); + Mov.memberUse(); +} + +void testExplicitCopy() { + Movable Mov{}; + constRefReceiver(Movable{Mov}); + // CHECK-MESSAGES: [[@LINE-1]]:28: warning: Parameter 'Mov' is copied on last use, consider moving it instead. [performance-unnecessary-copy-on-last-use] + // CHECK-FIXES: constRefReceiver(Movable{std::move(Mov)}); +} + +Movable testReturn() { + Movable Mov{}; + return Mov; // no warning, copy elision +} + +Movable testReturn2(Movable && Mov, bool F) { + return F? Mov: Movable{}; + // CHECK-MESSAGES: [[@LINE-1]]:13: warning: Parameter 'Mov' is copied on last use, consider moving it instead. [performance-unnecessary-copy-on-last-use] + // CHECK-FIXES: return F? std::move(Mov): Movable{}; +} + +/* +Todo (improvement): +Currently the check does not find last usages of fields of local objects. + +struct MoveOwner{ + Movable Mov{}; +}; + +void testFieldMove(){ + MoveOwner Owner{}; + valueReceiver(Owner.Mov); + Owner.Mov = Movable{}; + valueReceiver(Owner.Mov); + // DISABLED-CHECK-MESSAGES: [[@LINE-1]]:17: warning: Parameter 'Owner.Mov' is copied on last use, consider moving it instead. [performance-unnecessary-copy-on-last-use] + // DISABLED-CHECK-FIXES: valueReceiver(std::move(Owner.Mov)); +} +*/ + +/* +Todo (improvement): +Currently a heuristic detection of guards is not implemented, so this test is disabled +But before this is done, the check should not be used for automatic fixing + +using NoMove = std::shared_ptr>; + +void shareMutex(NoMove Nmov); + +void testNoMove(std::mutex& M, int& I) { + NoMove Nmov = std::make_shared>(M); + shareMutex(Nmov); + I = 42; +} +*/