Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -421,6 +421,20 @@ - ``-Wformat`` will no longer suggest a no-op fix-it for fixing scoped enum format warnings. Instead, it will suggest casting the enum object to the type specified in the format string. +- Clang now warns when the class template argument deduction deduces the type to be the same as the initializer + of a direct-initialization. Turn this on with ``-Wctad-selects-copy``. If the class template is in a system header + and a reachable explicit deduction guide is present, the warning is suppressed. + (`#63288 `_). + + .. code-block:: c++ + + auto vec = std::vector{42}; + auto v = std::vector{vec}; // no warning, user-defined deduction guides exist + // and the template is in a system header + + std::reverse_iterator it1 = v.rbegin(); // no warning, copy-initialization + auto it2 = std::reverse_iterator{v.rbegin()}; // warns, the intent might be the next line + auto it3 = std::reverse_iterator(v.rbegin()); // no warning, no CTAD Bug Fixes in This Version ------------------------- Index: clang/include/clang/Basic/DiagnosticGroups.td =================================================================== --- clang/include/clang/Basic/DiagnosticGroups.td +++ clang/include/clang/Basic/DiagnosticGroups.td @@ -1380,6 +1380,10 @@ def CrossTU : DiagGroup<"ctu">; def CTADMaybeUnsupported : DiagGroup<"ctad-maybe-unsupported">; +def CTADSelectsCopy : DiagGroup<"ctad-selects-copy">; +def CTADMaybeUnintended : DiagGroup<"ctad-maybe-unintended", [ + CTADMaybeUnsupported, CTADSelectsCopy + ]>; def FortifySource : DiagGroup<"fortify-source">; Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2488,6 +2488,10 @@ InGroup, DefaultIgnore; def note_suppress_ctad_maybe_unsupported : Note< "add a deduction guide to suppress this warning">; +def warn_ctad_selects_copy : Warning< + "%select{move|copy}0-constructing not wrapping a %1, " + "use copy-%select{list-|}2initialization to suppress this warning">, + InGroup, DefaultIgnore; // C++14 deduced return types Index: clang/lib/Sema/SemaInit.cpp =================================================================== --- clang/lib/Sema/SemaInit.cpp +++ clang/lib/Sema/SemaInit.cpp @@ -19,6 +19,7 @@ #include "clang/Basic/CharInfo.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Lex/Preprocessor.h" #include "clang/Sema/Designator.h" #include "clang/Sema/EnterExpressionEvaluationContext.h" #include "clang/Sema/Initialization.h" @@ -10908,6 +10909,18 @@ diag::warn_cxx14_compat_class_template_argument_deduction) << TSInfo->getTypeLoc().getSourceRange() << 1 << DeducedType; + // Warn if the copy deduction candidate is selected in direct-initialization. + auto DiagnoseCTADCopy = [&] { + if (auto IK = Kind.getKind(); + IK <= InitializationKind::IK_DirectList && + cast(Best->Function) + ->getDeductionCandidateKind() == DeductionCandidate::Copy) { + Diag(Kind.getLocation(), diag::warn_ctad_selects_copy) + << Kind.getRange() << Inits.front()->isLValue() << DeducedType + << !ListInit; + } + }; + // Warn if CTAD was used on a type that does not have any user-defined // deduction guides. if (!FoundDeductionGuide) { @@ -10915,6 +10928,9 @@ diag::warn_ctad_maybe_unsupported) << TemplateName; Diag(Template->getLocation(), diag::note_suppress_ctad_maybe_unsupported); + DiagnoseCTADCopy(); + } else if (!PP.getSourceManager().isInSystemHeader(Template->getLocation())) { + DiagnoseCTADCopy(); } return DeducedType; Index: clang/test/SemaTemplate/ctad.cpp =================================================================== --- clang/test/SemaTemplate/ctad.cpp +++ clang/test/SemaTemplate/ctad.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++17 -verify %s +// RUN: %clang_cc1 -std=c++23 -verify %s namespace pr41427 { template class A { @@ -44,3 +44,32 @@ }; D z = {Z(), {}}; } + +namespace warn_ctad_copy { +#pragma clang diagnostic push +#pragma clang diagnostic warning "-Wctad-selects-copy" +// like std::revrse_iterator, the motivating example +template struct CTAD { + template + requires(!__is_same(From, T) && __is_convertible_to(From, T)) + CTAD(const CTAD&) {} // #1 + CTAD(T) {} +}; +CTAD(void) -> CTAD; // does not suppress -Wctad-selects-copy + +CTAD& fc1(); +CTAD fc2(), + c1 = fc1(); // uses #1 +CTAD c2 = fc2(), // OK, uses copy-initialization + c3 = CTAD(4.2); // OK, copy deduction candidate not selected +CTAD c4{fc2()}; // test prvalue expression +// expected-warning@-1{{move-constructing not wrapping a 'CTAD' \ +(aka 'warn_ctad_copy::CTAD'), use copy-list-initialization to suppress this warning}} +CTAD c5 = CTAD{fc1()}; // test lvalue expression +// expected-warning@-1{{copy-constructing not wrapping a 'CTAD' \ +(aka 'warn_ctad_copy::CTAD'), use copy-list-initialization to suppress this warning}} +auto c6 = CTAD((CTAD&&)c5); // test xvalue expression +// expected-warning@-1{{move-constructing not wrapping a 'CTAD' \ +(aka 'warn_ctad_copy::CTAD'), use copy-initialization to suppress this warning}} +#pragma clang diagnostic pop +} // namespace warn_ctad_copy