Skip to content

[clang] Implement P2582R1: CTAD from inherited constructors #98788

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3f0dfdf
[clang] Implement P2582R1: CTAD from inherited constructors
antangelo Jun 1, 2024
9d3c664
Fix PR comment
antangelo Jul 16, 2024
35d7284
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Jul 16, 2024
2b537b5
clang-format
antangelo Jul 16, 2024
db8853a
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Jul 19, 2024
c17fe15
Remove QualType reference and set mapper template param to implicit
antangelo Jul 19, 2024
22f8327
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Jul 22, 2024
2ede0b3
Update test notes to account for canonical template arg changes
antangelo Jul 22, 2024
cd0e4de
Rely on Using decls instead of bases for determining inherited constr…
antangelo Jul 23, 2024
d77c3d2
Use static_assert(__is_same(...)) to verify types in tests
antangelo Jul 23, 2024
5c272cf
Update comment for BuildDeductionGuideForTypeAlias and fix nits
antangelo Jul 23, 2024
c89b4ea
Use enum to specify deduction guide source kind
antangelo Jul 23, 2024
b9fb81b
Fix parameter pack case
antangelo Jul 23, 2024
993d9d6
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Jul 28, 2024
b44f26a
clang-format
antangelo Jul 28, 2024
167d6a2
Merge branch 'main' into p2582r1
antangelo Aug 1, 2024
819cbb1
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Aug 24, 2024
ea6a946
Address PR feedback
antangelo Aug 24, 2024
18b8161
Run clang-format
antangelo Aug 24, 2024
a28bb6f
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Sep 7, 2024
4acd25e
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Sep 7, 2024
471a421
Address nits, check base template deducibility, clone TPL for partial…
antangelo Sep 10, 2024
c7071c8
clang-format
antangelo Sep 10, 2024
ca70caf
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Sep 10, 2024
b9dff6f
Merge branch 'main' of github.com:llvm/llvm-project into p2582r1
antangelo Sep 27, 2024
121870d
Merge branch 'main' of github.com:llvm/llvm-project into p2582r1
antangelo Oct 20, 2024
da2a9cf
clang-format
antangelo Oct 20, 2024
48d2bb4
Fix constraint substitution during param list cloning
antangelo Oct 20, 2024
0245459
PR feedback
antangelo Oct 21, 2024
ec4e5c6
clang-format
antangelo Oct 21, 2024
f36a4cf
Address PR feedback
antangelo Oct 28, 2024
a2c8167
clang-format
antangelo Oct 28, 2024
4ca159d
Merge branch 'main' of github.com:llvm/llvm-project into p2582r1
antangelo Oct 28, 2024
16465d3
Fix ASTImporterTest
antangelo Oct 28, 2024
cfbb7a0
Change cxx_status to partial, comment about dependent using-declarators
antangelo Oct 28, 2024
5fe08dc
Merge branch 'main' of github.com:llvm/llvm-project into p2582r1
antangelo Oct 28, 2024
207359b
Address PR feedback and fix crash when declaring inherited/alias DG f…
antangelo Nov 21, 2024
b04742b
clang-format
antangelo Nov 21, 2024
f23d838
Merge remote-tracking branch 'upstream/main' into p2582r1
antangelo Nov 21, 2024
f0faf97
Pass context into getInjectedTemplateArgs
antangelo Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -219,14 +219,14 @@ C++2c Feature Support
C++23 Feature Support
^^^^^^^^^^^^^^^^^^^^^
- Removed the restriction to literal types in constexpr functions in C++23 mode.
- Implemented `P2582R1: Wording for class template argument deduction from inherited constructors <https://wg21.link/P2582R1>`_.

- Extend lifetime of temporaries in mem-default-init for P2718R0. Clang now fully
supports `P2718R0 Lifetime extension in range-based for loops <https://wg21.link/P2718R0>`_.

C++20 Feature Support
^^^^^^^^^^^^^^^^^^^^^


Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
47 changes: 44 additions & 3 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -1960,24 +1960,44 @@ class ExplicitSpecifier {
class CXXDeductionGuideDecl : public FunctionDecl {
void anchor() override;

public:
// Represents the relationship between this deduction guide and the
// deduction guide that it was generated from (or lack thereof).
// See the SourceDeductionGuide member for more details.
enum class SourceDeductionGuideKind : unsigned char {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
enum class SourceDeductionGuideKind : unsigned char {
enum class SourceDeductionGuideKind : uint8_t {

I thought I saw elsewhere we do this reasonably consistently? Else, feel free to ignore.

None,
Alias,
InheritedConstructor,
};

private:
CXXDeductionGuideDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc,
ExplicitSpecifier ES,
const DeclarationNameInfo &NameInfo, QualType T,
TypeSourceInfo *TInfo, SourceLocation EndLocation,
CXXConstructorDecl *Ctor, DeductionCandidate Kind,
Expr *TrailingRequiresClause)
Expr *TrailingRequiresClause,
const CXXDeductionGuideDecl *GeneratedFrom,
SourceDeductionGuideKind SourceKind)
: FunctionDecl(CXXDeductionGuide, C, DC, StartLoc, NameInfo, T, TInfo,
SC_None, false, false, ConstexprSpecKind::Unspecified,
TrailingRequiresClause),
Ctor(Ctor), ExplicitSpec(ES) {
Ctor(Ctor), ExplicitSpec(ES),
SourceDeductionGuide(GeneratedFrom, SourceKind) {
if (EndLocation.isValid())
setRangeEnd(EndLocation);
setDeductionCandidateKind(Kind);
}

CXXConstructorDecl *Ctor;
ExplicitSpecifier ExplicitSpec;
// The deduction guide, if any, that this deduction guide was generated from,
// in the case of alias template deduction or CTAD from inherited
// constructors. The SourceDeductionGuideKind member indicates which of these
// sources applies, or is None otherwise.
llvm::PointerIntPair<const CXXDeductionGuideDecl *, 2,
SourceDeductionGuideKind>
SourceDeductionGuide;
void setExplicitSpecifier(ExplicitSpecifier ES) { ExplicitSpec = ES; }

public:
Expand All @@ -1990,7 +2010,9 @@ class CXXDeductionGuideDecl : public FunctionDecl {
TypeSourceInfo *TInfo, SourceLocation EndLocation,
CXXConstructorDecl *Ctor = nullptr,
DeductionCandidate Kind = DeductionCandidate::Normal,
Expr *TrailingRequiresClause = nullptr);
Expr *TrailingRequiresClause = nullptr,
const CXXDeductionGuideDecl *SourceDG = nullptr,
SourceDeductionGuideKind SK = SourceDeductionGuideKind::None);

static CXXDeductionGuideDecl *CreateDeserialized(ASTContext &C,
GlobalDeclID ID);
Expand All @@ -2010,6 +2032,25 @@ class CXXDeductionGuideDecl : public FunctionDecl {
/// this is an implicit deduction guide.
CXXConstructorDecl *getCorrespondingConstructor() const { return Ctor; }

/// Get the deduction guide from which this deduction guide was generated,
/// if it was generated as part of alias template deduction or from an
/// inherited constructor.
const CXXDeductionGuideDecl *getSourceDeductionGuide() const {
return SourceDeductionGuide.getPointer();
}

void setSourceDeductionGuide(CXXDeductionGuideDecl *DG) {
SourceDeductionGuide.setPointer(DG);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to assert for !DG?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opted not to assert here and in setSourceDeductionGuideKind because it makes the ASTReader changes more readable (which otherwise would have to do a check on at least one of these fields before setting it on the incoming CXXDeductionGuideDecl node, but maybe that's preferable).

}

SourceDeductionGuideKind getSourceDeductionGuideKind() const {
return SourceDeductionGuide.getInt();
}

void setSourceDeductionGuideKind(SourceDeductionGuideKind SK) {
SourceDeductionGuide.setInt(SK);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PERHAPS here too? Assert SK == None?

}

void setDeductionCandidateKind(DeductionCandidate K) {
FunctionDeclBits.DeductionCandidateKind = static_cast<unsigned char>(K);
}
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -4859,6 +4859,10 @@ def note_ovl_candidate_underqualified : Note<
"make %2 equal %1">;
def note_ovl_candidate_substitution_failure : Note<
"candidate template ignored: substitution failure%0%1">;
def note_ovl_candidate_inherited_constructor_deduction_failure: Note<
"candidate template ignored: could not deduce template arguments for %0 from inherited constructor of %1%2">;
def note_ovl_candidate_inherited_constructor_source: Note<
"inherited from %select{|implicit }0deduction guide declared here">;
def note_ovl_candidate_disabled_by_enable_if : Note<
"candidate template ignored: disabled by %0%1">;
def note_ovl_candidate_disabled_by_requirement : Note<
Expand Down
8 changes: 5 additions & 3 deletions clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3998,14 +3998,16 @@ ExpectedDecl ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
importExplicitSpecifier(Err, Guide->getExplicitSpecifier());
CXXConstructorDecl *Ctor =
importChecked(Err, Guide->getCorrespondingConstructor());
const CXXDeductionGuideDecl *SourceDG =
importChecked(Err, Guide->getSourceDeductionGuide());
if (Err)
return std::move(Err);
if (GetImportedOrCreateDecl<CXXDeductionGuideDecl>(
ToFunction, D, Importer.getToContext(), DC, ToInnerLocStart, ESpec,
NameInfo, T, TInfo, ToEndLoc, Ctor))
NameInfo, T, TInfo, ToEndLoc, Ctor,
Guide->getDeductionCandidateKind(), TrailingRequiresClause,
SourceDG, Guide->getSourceDeductionGuideKind()))
return ToFunction;
cast<CXXDeductionGuideDecl>(ToFunction)
->setDeductionCandidateKind(Guide->getDeductionCandidateKind());
} else {
if (GetImportedOrCreateDecl(
ToFunction, D, Importer.getToContext(), DC, ToInnerLocStart,
Expand Down
13 changes: 8 additions & 5 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2246,18 +2246,21 @@ CXXDeductionGuideDecl *CXXDeductionGuideDecl::Create(
ASTContext &C, DeclContext *DC, SourceLocation StartLoc,
ExplicitSpecifier ES, const DeclarationNameInfo &NameInfo, QualType T,
TypeSourceInfo *TInfo, SourceLocation EndLocation, CXXConstructorDecl *Ctor,
DeductionCandidate Kind, Expr *TrailingRequiresClause) {
return new (C, DC)
CXXDeductionGuideDecl(C, DC, StartLoc, ES, NameInfo, T, TInfo,
EndLocation, Ctor, Kind, TrailingRequiresClause);
DeductionCandidate Kind, Expr *TrailingRequiresClause,
const CXXDeductionGuideDecl *GeneratedFrom,
SourceDeductionGuideKind SourceKind) {
return new (C, DC) CXXDeductionGuideDecl(
C, DC, StartLoc, ES, NameInfo, T, TInfo, EndLocation, Ctor, Kind,
TrailingRequiresClause, GeneratedFrom, SourceKind);
}

CXXDeductionGuideDecl *
CXXDeductionGuideDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
return new (C, ID) CXXDeductionGuideDecl(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Exprs we usually have a CreateEmpty that has an empty/empty-ish private constructor associated with it. or something like that.... this is very much making me wish we had the same for this one.

C, nullptr, SourceLocation(), ExplicitSpecifier(), DeclarationNameInfo(),
QualType(), nullptr, SourceLocation(), nullptr,
DeductionCandidate::Normal, nullptr);
DeductionCandidate::Normal, nullptr,
/*GeneratedFrom=*/nullptr, SourceDeductionGuideKind::None);
}

RequiresExprBodyDecl *RequiresExprBodyDecl::Create(
Expand Down
77 changes: 76 additions & 1 deletion clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10612,6 +10612,40 @@ bool clang::isBetterOverloadCandidate(
auto *Guide1 = dyn_cast_or_null<CXXDeductionGuideDecl>(Cand1.Function);
auto *Guide2 = dyn_cast_or_null<CXXDeductionGuideDecl>(Cand2.Function);
if (Guide1 && Guide2) {
// -- F1 and F2 are generated from class template argument deduction
// for a class D, and F2 is generated from inheriting constructors
// from a base class of D while F1 is not, ...
bool G1Inherited = Guide1->getSourceDeductionGuide() &&
Guide1->getSourceDeductionGuideKind() ==
CXXDeductionGuideDecl::SourceDeductionGuideKind::
InheritedConstructor;
bool G2Inherited = Guide2->getSourceDeductionGuide() &&
Guide2->getSourceDeductionGuideKind() ==
CXXDeductionGuideDecl::SourceDeductionGuideKind::
InheritedConstructor;
if (Guide1->isImplicit() && Guide2->isImplicit() &&
G1Inherited != G2Inherited) {
const FunctionProtoType *FPT1 =
Guide1->getType()->getAs<FunctionProtoType>();
const FunctionProtoType *FPT2 =
Guide2->getType()->getAs<FunctionProtoType>();
assert(FPT1 && FPT2);

// ... and for each explicit function argument, the parameters of F1 and
// F2 are either both ellipses or have the same type
if (FPT1->isVariadic() == FPT2->isVariadic() &&
FPT1->getNumParams() == FPT2->getNumParams()) {
Comment on lines +10636 to +10637
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong compared to the comment -- the comment says we should be looking at each explicit function argument, but this predicate only handles the case where both functions are variadic and have the same number of params.

Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my understanding of the implications of the wording, if they do not have the same number of parameters, then at least one of the parameters must not have the same type (since the corresponding parameter in the other function wouldn't exist). If this is the case, then we don't need to do the work of checking all the parameters individually.

The variadic check is that the functions are either both variadic, or both not variadic, for handling of the ellipses parameters.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we can't just check the types of the functions here instead? I would assume their prototypes to compare equal. That said, the wording of parameters are both ellipses or have the same type seems intentionally designed for the purpose of excluding the return type (or, perhaps reference/cv specifiers?)?

If so, I think I'd rather a variant of hasSameType that for something like, hasSameParameterList. At that point, we could have IT check for similar variadics as well. We probably want to make sure that (int, ...) works too.

Also, I don't think the initial check is right? Shouldn't that be an 'or' there instead of '&&'? If both are NumParams == 0 && isVariadic, then we want to hit this condition as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return types will not be equal as the inherited deduction guides will have some typename CC<R>::type as the return type, while non-inherited deduction guides will just have a template specialization type for the class template.

If both functions are variadic and have getNumParams() == 0, then that boolean expression evaluates to true right? Can you elaborate on what you think the behavior there should be? I may be misunderstanding what the standards wording on this part is asking.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could assert that getNumNonObjectParams is false (I don't think we can get explicit objects anywhere, right? Otherwise, the code looks correct, but I agree that something like a "haveSameNonObjectParameters" function would improve readability

bool ParamsHaveSameType =
llvm::all_of_zip(FPT1->getParamTypes(), FPT2->getParamTypes(),
[&S](const QualType &P1, const QualType &P2) {
return S.Context.hasSameType(P1, P2);
});

if (ParamsHaveSameType)
return G2Inherited;
}
}

// -- F1 is generated from a deduction-guide and F2 is not
if (Guide1->isImplicit() != Guide2->isImplicit())
return Guide2->isImplicit();
Expand Down Expand Up @@ -11755,6 +11789,35 @@ static void DiagnoseBadDeduction(Sema &S, NamedDecl *Found, Decl *Templated,
return;
}

// Errors in deduction guides from inherited constructors
// will manifest as substitution failures in the return type
// partial specialization, so we show a generic diagnostic
// in this case.
if (const auto *DG = dyn_cast<CXXDeductionGuideDecl>(Templated);
DG && DG->getSourceDeductionGuideKind() ==
CXXDeductionGuideDecl::SourceDeductionGuideKind::
InheritedConstructor) {
const CXXDeductionGuideDecl *Source = DG->getSourceDeductionGuide();
assert(Source &&
"Inherited constructor deduction guides must have a source");

auto GetDGDeducedTemplateType =
[](const CXXDeductionGuideDecl *DG) -> QualType {
return QualType(cast<ClassTemplateDecl>(DG->getDeducedTemplate())
->getTemplatedDecl()
->getTypeForDecl(),
0);
};

QualType DeducedRecordType = GetDGDeducedTemplateType(DG);
QualType InheritedRecordType = GetDGDeducedTemplateType(Source);
S.Diag(Templated->getLocation(),
diag::note_ovl_candidate_inherited_constructor_deduction_failure)
<< DeducedRecordType << InheritedRecordType << TemplateArgString
<< DG->getParametersSourceRange();
return;
}

// Format the SFINAE diagnostic into the argument string.
// FIXME: Add a general mechanism to include a PartialDiagnostic *'s
// formatted message in another diagnostic.
Expand Down Expand Up @@ -11969,9 +12032,21 @@ static void DiagnoseFailedExplicitSpec(Sema &S, OverloadCandidate *Cand) {
}

static void NoteImplicitDeductionGuide(Sema &S, FunctionDecl *Fn) {
auto *DG = dyn_cast<CXXDeductionGuideDecl>(Fn);
const auto *DG = dyn_cast<CXXDeductionGuideDecl>(Fn);
if (!DG)
return;
// The definition of inherited constructor deduction guides is not
// of particular use to end users, as the CC<R> return type cannot
// be manually constructed. Instead, we show the guide we started with.
while (
DG->getSourceDeductionGuideKind() ==
CXXDeductionGuideDecl::SourceDeductionGuideKind::InheritedConstructor) {
DG = DG->getSourceDeductionGuide();
S.Diag(DG->getLocation(),
diag::note_ovl_candidate_inherited_constructor_source)
<< (DG->isImplicit() ? 1 : 0);
}

TemplateDecl *OriginTemplate =
DG->getDeclName().getCXXDeductionGuideTemplate();
// We want to always print synthesized deduction guides for type aliases.
Expand Down
Loading