[cfe-commits] [PATCH] Implement C++0x deduced auto type
Richard Smith
richard at metafoo.co.uk
Sat Feb 19 19:26:03 PST 2011
On Fri, February 18, 2011 17:52, Douglas Gregor wrote:
> On Feb 17, 2011, at 6:26 PM, Richard Smith wrote:
>> The attached patch implements the C++0x deduced 'auto' type feature.
>> This should fix the following PRs:
>>
>> Bug 8738 - C++'0x auto not implemented
>> Bug 9060 - Late-specified return type accepted for parenthesized
>> declarator Bug 9132 - clang accepts declarators other than a literal
>> 'auto' as the return type of a function with a trailing-return-type
>>
>> (The latter two were fixed in order to allow more comprehensive testing
>> of deduced auto!)
[snip]
> I have a bunch of comments below, most of which
> are for relatively small issues or questions. In all cases, I'm perfectly
> happy to have this patch go in as-is (or with the tiny tweaks I've
> requested), and save some of the non-trivial things for follow-up
> commits.
Great, I've checked it in with a few of the tweaks you suggest below as
r126069.
> Specific comments on your patch:
>
>
> Index: test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p6.cpp
> ===================================================================
> --- test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p6.cpp (revision 0)
> +++ test/CXX/dcl.dcl/dcl.spec/dcl.type/dcl.spec.auto/p6.cpp (revision 0)
> @@ -0,0 +1,60 @@
> [snip]
> + const auto **fail2(p); // desired-error {{variable 'fail2' with type
> 'auto const **' has incompatible initializer of type 'int **'}}
> expected-error {{cannot initialize}} +}
>
>
> The "desired-error" is cute, but please put a FIXME to indicate that the
> diagnostic here isn't any good. This is due to PR9233, right?
Done. And the same for a couple of other poor diagnostics.
> Index: include/clang/AST/Decl.h
> ===================================================================
> --- include/clang/AST/Decl.h (revision 125795)
> +++ include/clang/AST/Decl.h (working copy)
> @@ -646,6 +646,10 @@
> /// \brief Whether this local variable could be allocated in the return
> /// slot of its function, enabling the named return value optimization
> (NRVO).
> bool NRVOVariable : 1; +
> + /// \brief Whether this variable has a deduced C++0x auto type for
> which we're + /// currently parsing the initializer.
> + bool ParsingAutoInit : 1;
>
>
> It's unfortunate that this temporary bit has to live forever in the AST.
> Did you consider an alternative, such as a SmallPtrSet inside Sema that
> tracks the (few, relatively rare) VarDecls that are waiting for their
> initializers? (I guess in the future, FieldDecls must also be
> considered).
Using a spare bit on VarDecl seemed slightly simpler, but FieldDecls have
me sold on SmallPtrSet now :) I'll put a patch together for that.
> Index: include/clang/AST/Type.h
> ===================================================================
> --- include/clang/AST/Type.h (revision 125795)
> +++ include/clang/AST/Type.h (working copy)
> @@ -3014,6 +3015,45 @@
> static bool classof(const SubstTemplateTypeParmPackType *T) { return true;
> }
> };
>
>
> +/// \brief Represents a C++0x auto type.
> +///
> +/// These types are usually a placeholder for a deduced type. However,
> within +/// templates and before the initializer is attached, there is no
> deduced type +/// and an auto type is type-dependent and canonical.
> +class AutoType : public Type, public llvm::FoldingSetNode {
> + AutoType(QualType DeducedType)
> + : Type(Auto, DeducedType.isNull() ? QualType(this, 0) : DeducedType,
> + DeducedType.isNull() || DeducedType->isDependentType(),
> + /*VariablyModified=*/false, /*ContainsParameterPack=*/false) {
> }
>
>
> The deduced type can never be dependent, because we shouldn't be trying
> to deduce anything from a type-dependent initializer. So, I think the "||
> DeducedType->isDependentType()" be be removed and, instead, add an assert
> for DeducedType.isNull() || !DeducedType->isDependentType().
Done. I had hoped we could deduce type T for "auto v = T();" (within a
template<typename T>) but I don't think that can work.
> Index: lib/Sema/SemaDecl.cpp
> ===================================================================
> --- lib/Sema/SemaDecl.cpp (revision 125795)
> +++ lib/Sema/SemaDecl.cpp (working copy)
> @@ -4500,6 +4525,24 @@
> return; }
>
>
> + if (TypeMayContainAuto && VDecl->getType()->getContainedAutoType()) {
> + VDecl->setParsingAutoInit(false);
> +
> + QualType DeducedType;
> + if (!DeduceAutoType(VDecl->getType(), Init, DeducedType)) {
> + Diag(VDecl->getLocation(), diag::err_auto_var_deduction_failure)
> + << VDecl->getDeclName() << VDecl->getType() << Init->getType()
> + << Init->getSourceRange();
> + RealDecl->setInvalidDecl();
> + return;
> + }
> + VDecl->setType(DeducedType);
> +
> + // If this is a redeclaration, check that the type we just deduced
> matches + // the previously declared type.
> + if (VarDecl *Old = VDecl->getPreviousDeclaration())
> + MergeVarDeclTypes(VDecl, Old);
> + }
>
>
> It would be useful to cite the appropriate C++0x standard clause here.
Done.
> Index: lib/Sema/SemaTemplateDeduction.cpp
> ===================================================================
> --- lib/Sema/SemaTemplateDeduction.cpp (revision 125795)
> +++ lib/Sema/SemaTemplateDeduction.cpp (working copy)
> @@ -22,6 +22,7 @@
> #include "clang/AST/Expr.h"
> #include "clang/AST/ExprCXX.h"
> #include "llvm/ADT/BitVector.h"
> +#include "TreeTransform.h"
> #include <algorithm>
>
>
> namespace clang { @@ -2445,21 +2446,22 @@
> ParamType = ParamType.getLocalUnqualifiedType();
> const ReferenceType *ParamRefType = ParamType->getAs<ReferenceType>(); if
> (ParamRefType) {
> + QualType PointeeType = ParamRefType->getPointeeType();
> +
> // [C++0x] If P is an rvalue reference to a cv-unqualified
> // template parameter and the argument is an lvalue, the type
> // "lvalue reference to A" is used in place of A for type
> // deduction.
> - if (const RValueReferenceType *RValueRef
> - =
> dyn_cast<RValueReferenceType>(ParamType)) { - if
> (!RValueRef->getPointeeType().getQualifiers() &&
> - isa<TemplateTypeParmType>(RValueRef->getPointeeType()) &&
> + if (isa<RValueReferenceType>(ParamType)) {
> + if (!PointeeType.getQualifiers() &&
> + isa<TemplateTypeParmType>(PointeeType) &&
> Arg->Classify(S.Context).isLValue())
> ArgType = S.Context.getLValueReferenceType(ArgType);
> }
>
>
> // [...] If P is a reference type, the type referred to by P is used
> // for type deduction.
> - ParamType = ParamRefType->getPointeeType();
> + ParamType = PointeeType;
> }
>
>
> // Overload sets usually make this parameter an undeduced
>
>
> This is unrelated to auto, right? It also seems slightly dangerous, since
> we're really looking for the specific written form "T&&" (hence the
> dyn_cast on ParamType), rather than something that might be a typedef of
> "T&&".
I was in this area trying to fix deduction of "auto &&x = lvalue;", but
ended up not putting the fix here. The changes above should be a no-op,
just changing an unnecessary dyn_cast to isa.
Incidentally, are we really supposed to treat:
template<typename T> void f(T &&v);
differently from:
template<typename T> void f(T (&&v));
? FWIW, g++ treats them the same.
> @@ -2946,6 +2948,88 @@
> QualType(), Specialization, Info);
> }
>
>
> +namespace {
> + class SubstituteAutoTransform :
> + public TreeTransform<SubstituteAutoTransform> {
> + QualType Replacement;
> + public:
> + SubstituteAutoTransform(Sema &SemaRef, QualType Replacement) :
> + TreeTransform<SubstituteAutoTransform>(SemaRef),
> Replacement(Replacement) {
> + }
>
>
> Please add a comment for the SubstituteAutoTransform class.
Done.
> + QualType TransformAutoType(TypeLocBuilder &TLB, AutoTypeLoc TL) {
> + // Some of the template deduction code expects to see unadorned
> + // TemplateTypeParmTypes, so don't wrap them in AutoType.
> + if (isa<TemplateTypeParmType>(Replacement)) {
> + QualType Result = Replacement;
> + TemplateTypeParmTypeLoc NewTL =
> TLB.push<TemplateTypeParmTypeLoc>(Result);
> + NewTL.setNameLoc(TL.getNameLoc());
> + return Result;
> + } else {
> + QualType Result = RebuildAutoType(Replacement);
> + AutoTypeLoc NewTL = TLB.push<AutoTypeLoc>(Result);
> + NewTL.setNameLoc(TL.getNameLoc());
> + return Result;
> + }
> + }
> + };
> +}
>
>
> Is the comment on the "if" branch wrong? It seems that we wouldn't be
> performing template argument deduction using something that was
> originally an 'auto' type as the "P" in the deduction. However, it does
> seem that this "if" branch is critical for Sema::DeduceAutoType to work
> properly.
Yes, the comment is right. I've expanded it somewhat in the checked-in code:
// If we're building the type pattern to deduce against, don't wrap the
// substituted type in an AutoType. Certain template deduction rules
// apply only when a template type parameter appears directly (and not if
// the parameter is found through desugaring). For instance:
// auto &&lref = lvalue;
// must transform into "rvalue reference to T" not "rvalue reference to
// auto type deduced as T" in order for [temp.deduct.call]p3 to apply.
> +/// \brief Deduce the type for an auto type-specifier (C++0x
> [dcl.spec.auto]p6)
> +///
> +/// \param Type the type pattern using the auto type-specifier.
> +///
> +/// \param Init the initializer for the variable whose type is to be
> deduced. +///
> +/// \param Result if type deduction was successful, this will be set to
> the +/// deduced type. This may still contain undeduced autos if the type
> is +/// dependent.
> +///
> +/// \returns true if deduction succeeded, false if it failed.
> +bool
> +Sema::DeduceAutoType(QualType Type, Expr *Init, QualType &Result) {
> + if (Init->isTypeDependent()) {
> + Result = Type;
> + return true;
> + }
> +
> + SourceLocation Loc = Init->getExprLoc();
> +
> + LocalInstantiationScope InstScope(*this);
> +
> + // Build template<class TemplParam> void Func(FuncParam);
> + NamedDecl *TemplParam
> + = TemplateTypeParmDecl::Create(Context, 0, Loc, 0, 0, 0, false,
> false); + TemplateParameterList *TemplateParams
> + = TemplateParameterList::Create(Context, Loc, Loc, &TemplParam, 1,
> Loc);
> +
> + QualType TemplArg = Context.getTemplateTypeParmType(0, 0, false);
> + QualType FuncParam =
> + SubstituteAutoTransform(*this, TemplArg).TransformType(Type);
> +
> + // Deduce type of TemplParam in Func(Init)
> + llvm::SmallVector<DeducedTemplateArgument, 1> Deduced;
> + Deduced.resize(1);
> + QualType InitType = Init->getType();
> + unsigned TDF = 0;
> + if (AdjustFunctionParmAndArgTypesForDeduction(*this, TemplateParams,
> + FuncParam, InitType,
> Init,
> + TDF))
> + return false;
> +
> + TemplateDeductionInfo Info(Context, Loc);
> + if (::DeduceTemplateArguments(*this, TemplateParams,
> + FuncParam, InitType, Info, Deduced,
> + TDF))
> + return false;
> +
> + QualType DeducedType = Deduced[0].getAsType();
> + if (DeducedType.isNull())
> + return false;
> +
> + Result = SubstituteAutoTransform(*this,
> DeducedType).TransformType(Type);
> + return true;
> +}
> +
>
>
> 1-1 mapping between standard and implementation, I like it! I do wonder
> if we could reduce the memory footprint a bit by, e.g., allocating the
> template parameter list and template type parameter on the stack rather
> than in the never-to-be-freed ASTContext. This could be a follow-up
> optimization.
OK, I'll look into it.
> Index: lib/Sema/SemaTemplate.cpp
> ===================================================================
> --- lib/Sema/SemaTemplate.cpp (revision 125795)
> +++ lib/Sema/SemaTemplate.cpp (working copy)
> @@ -2778,6 +2778,10 @@
> return false; }
>
>
> +bool UnnamedLocalNoLinkageFinder::VisitAutoType(const AutoType*) {
> + return false;
> +}
> +
>
>
> Shouldn't this visit the deduced type, if there is one?
Yes, but this code is unreachable in c++0x mode. I've changed it anyway,
in case auto-the-type-specifier is ever made available to c++03.
> Index: lib/Sema/SemaType.cpp
> ===================================================================
> --- lib/Sema/SemaType.cpp (revision 125795)
> +++ lib/Sema/SemaType.cpp (working copy)
> @@ -1631,6 +1615,32 @@
> D.setInvalidType(true);
> }
>
>
> + // Check for auto functions and trailing return type and adjust
> the + // return type accordingly.
> + if (getLangOptions().CPlusPlus0x && !D.isInvalidType()) {
> + // trailing-return-type is only required if we're declaring a
> function, + // and not, for instance, a pointer to a function.
> + if (D.getDeclSpec().getTypeSpecType() == DeclSpec::TST_auto &&
> + !FTI.TrailingReturnType && chunkIndex == 0) {
> + Diag(D.getDeclSpec().getTypeSpecTypeLoc(),
> + diag::err_auto_missing_trailing_return);
> + T = Context.IntTy;
> + D.setInvalidType(true);
> + } else if (FTI.TrailingReturnType) {
> + if (T.hasQualifiers() || !isa<AutoType>(T)) {
> + // T must be exactly 'auto' at this point. See CWG issue 681.
> + Diag(D.getDeclSpec().getTypeSpecTypeLoc(),
> + diag::err_trailing_return_without_auto)
> + << T << D.getDeclSpec().getSourceRange();
> + D.setInvalidType(true);
> + }
> +
> + T = GetTypeFromParser(
> + ParsedType::getFromOpaquePtr(FTI.TrailingReturnType),
> + &ReturnTypeInfo);
> + }
> + }
> +
>
>
> Only a trivial comment here: I know the check was there before, there's
> no need to check the CPlusPlus0x flag, since we won't see TST_auto or
> FTI.TrailingReturnType unless the parser wanted it. By not checking for
> C++0x here, we have the option of teaching the parser to be smarter about
> dialect issues, e.g., if it figures out that "auto" is being used as a
> type-specifier in C++98/03 code, it could warn and then parse it as a
> type-specifier.
Done.
> Index: lib/AST/ItaniumMangle.cpp
> ===================================================================
> --- lib/AST/ItaniumMangle.cpp (revision 125795)
> +++ lib/AST/ItaniumMangle.cpp (working copy)
> @@ -1313,9 +1313,6 @@
> assert(false && "Overloaded and dependent types shouldn't get to name
> mangling"); break; - case BuiltinType::UndeducedAuto:
> - assert(0 && "Should not see undeduced auto here");
> - break;
> case BuiltinType::ObjCId: Out << "11objc_object"; break; case
> BuiltinType::ObjCClass: Out << "10objc_class"; break;
> case BuiltinType::ObjCSel: Out << "13objc_selector"; break; @@ -1648,6
> +1645,12 @@
> Out << 'E';
> }
>
>
> +void CXXNameMangler::mangleType(const AutoType *T) {
> + QualType D = T->getDeducedType();
> + assert(!D.isNull() && "can't mangle undeduced auto type");
> + mangleType(D);
> +}
>
>
> Please make this an error (via the Diagnostics subsystem) rather than an
> assertion... I can think of evil ways to write code that would trip up
> here, which we should (but don't) diagnose as errors. I'd rather fail
> gracefully.
I'll tackle this as a follow-up. Can you suggest a test case?
> Index: lib/AST/ASTContext.cpp
> ===================================================================
> --- lib/AST/ASTContext.cpp (revision 125795)
> +++ lib/AST/ASTContext.cpp (working copy)
> @@ -2674,6 +2677,14 @@
> return QualType(dt, 0); }
>
>
> +/// getAutoType - Unlike many "get<Type>" functions, we don't unique
> +/// AutoType AST's.
> +QualType ASTContext::getAutoType(QualType DeducedType) const {
> + AutoType *at = new (*this, TypeAlignment) AutoType(DeducedType);
> + Types.push_back(at);
> + return QualType(at, 0);
> +}
>
>
> Why not unique 'auto' types when we have a deduced type?
>
>
> I can understand not uniquing when we have a dependent initializer (so
> the 'auto' has no deduced type for a "long" time), since we don't want
> 'x' and 'y' to look like they have the same type in:
>
>
> template<typename T, typename U> void f() { auto x = T(); auto y = U(); }
>
>
> If we could unique in the common cases---where we have a deduced type, or
> where we have no deduced type and we know we're not anywhere inside a
> template---we could save ourselves some memory.
Uniquing in the deduced case seems straightforward. I'll put together a
patch for that. Uniquing in the not-yet-initialized case seems as
dangerous as uniquing in the template case: x and y should have distinct
types here:
auto x = ({ auto y =
> None of the tests seem to cover the pointer-to-member case, e.g.,
>
> struct X { int &f(int); };
>
> void test(X &x) { auto X::*fp = &X::f; int &ir = (x.*fp)(17); }
>
> even though it seems to work perfectly well.
I've added some tests for this case.
Thanks,
Richard
More information about the cfe-commits
mailing list