Implement C++17 P0386R2, inline variables. (The 'inline' specifier gives a
variable weak discardable linkage and partially-ordered initialization, and is implied for constexpr static data members.) llvm-svn: 273754
This commit is contained in:
parent
b8da3a2bb2
commit
62f19e700d
|
@ -881,6 +881,12 @@ protected:
|
|||
/// variable; see isARCPseudoStrong() for details.
|
||||
unsigned ARCPseudoStrong : 1;
|
||||
|
||||
/// \brief Whether this variable is (C++1z) inline.
|
||||
unsigned IsInline : 1;
|
||||
|
||||
/// \brief Whether this variable has (C++1z) inline explicitly specified.
|
||||
unsigned IsInlineSpecified : 1;
|
||||
|
||||
/// \brief Whether this variable is (C++0x) constexpr.
|
||||
unsigned IsConstexpr : 1;
|
||||
|
||||
|
@ -1102,9 +1108,6 @@ public:
|
|||
/// definition of a static data member.
|
||||
bool isOutOfLine() const override;
|
||||
|
||||
/// \brief If this is a static data member, find its out-of-line definition.
|
||||
VarDecl *getOutOfLineDefinition();
|
||||
|
||||
/// isFileVarDecl - Returns true for file scoped variable declaration.
|
||||
bool isFileVarDecl() const {
|
||||
Kind K = getKind();
|
||||
|
@ -1250,6 +1253,24 @@ public:
|
|||
NonParmVarDeclBits.ARCPseudoStrong = ps;
|
||||
}
|
||||
|
||||
/// Whether this variable is (C++1z) inline.
|
||||
bool isInline() const {
|
||||
return isa<ParmVarDecl>(this) ? false : NonParmVarDeclBits.IsInline;
|
||||
}
|
||||
bool isInlineSpecified() const {
|
||||
return isa<ParmVarDecl>(this) ? false
|
||||
: NonParmVarDeclBits.IsInlineSpecified;
|
||||
}
|
||||
void setInlineSpecified() {
|
||||
assert(!isa<ParmVarDecl>(this));
|
||||
NonParmVarDeclBits.IsInline = true;
|
||||
NonParmVarDeclBits.IsInlineSpecified = true;
|
||||
}
|
||||
void setImplicitlyInline() {
|
||||
assert(!isa<ParmVarDecl>(this));
|
||||
NonParmVarDeclBits.IsInline = true;
|
||||
}
|
||||
|
||||
/// Whether this variable is (C++11) constexpr.
|
||||
bool isConstexpr() const {
|
||||
return isa<ParmVarDecl>(this) ? false : NonParmVarDeclBits.IsConstexpr;
|
||||
|
|
|
@ -339,12 +339,16 @@ def err_language_linkage_spec_not_ascii : Error<
|
|||
def warn_use_out_of_scope_declaration : Warning<
|
||||
"use of out-of-scope declaration of %0">;
|
||||
def err_inline_non_function : Error<
|
||||
"'inline' can only appear on functions">;
|
||||
"'inline' can only appear on functions%select{| and non-local variables}0">;
|
||||
def err_noreturn_non_function : Error<
|
||||
"'_Noreturn' can only appear on functions">;
|
||||
def warn_qual_return_type : Warning<
|
||||
"'%0' type qualifier%s1 on return type %plural{1:has|:have}1 no effect">,
|
||||
InGroup<IgnoredQualifiers>, DefaultIgnore;
|
||||
def warn_deprecated_redundant_constexpr_static_def : Warning<
|
||||
"out-of-line definition of constexpr static data member is redundant "
|
||||
"in C++17 and is deprecated">,
|
||||
InGroup<Deprecated>, DefaultIgnore;
|
||||
|
||||
def warn_decl_shadow :
|
||||
Warning<"declaration shadows a %select{"
|
||||
|
@ -1086,6 +1090,12 @@ def warn_cxx14_compat_static_assert_no_message : Warning<
|
|||
"static_assert with no message is incompatible with C++ standards before C++1z">,
|
||||
DefaultIgnore, InGroup<CXXPre1zCompat>;
|
||||
|
||||
def ext_inline_variable : ExtWarn<
|
||||
"inline variables are a C++1z extension">, InGroup<CXX1z>;
|
||||
def warn_cxx14_compat_inline_variable : Warning<
|
||||
"inline variables are incompatible with C++ standards before C++1z">,
|
||||
DefaultIgnore, InGroup<CXXPre1zCompat>;
|
||||
|
||||
def warn_inline_namespace_reopened_noninline : Warning<
|
||||
"inline namespace cannot be reopened as a non-inline namespace">;
|
||||
def err_inline_namespace_mismatch : Error<
|
||||
|
@ -4305,6 +4315,7 @@ def warn_undefined_internal : Warning<
|
|||
InGroup<DiagGroup<"undefined-internal">>;
|
||||
def warn_undefined_inline : Warning<"inline function %q0 is not defined">,
|
||||
InGroup<DiagGroup<"undefined-inline">>;
|
||||
def err_undefined_inline_var : Error<"inline variable %q0 is not defined">;
|
||||
def note_used_here : Note<"used here">;
|
||||
|
||||
def err_internal_linkage_redeclaration : Error<
|
||||
|
|
|
@ -73,10 +73,11 @@ inline void MarkVarDeclODRUsed(VarDecl *Var,
|
|||
// Keep track of used but undefined variables.
|
||||
// FIXME: We shouldn't suppress this warning for static data members.
|
||||
if (Var->hasDefinition(SemaRef.Context) == VarDecl::DeclarationOnly &&
|
||||
!Var->isExternallyVisible() &&
|
||||
!(Var->isStaticDataMember() && Var->hasInit())) {
|
||||
SourceLocation &old = SemaRef.UndefinedButUsed[Var->getCanonicalDecl()];
|
||||
if (old.isInvalid()) old = Loc;
|
||||
(!Var->isExternallyVisible() || Var->isInline()) &&
|
||||
!(Var->isStaticDataMember() && Var->hasInit())) {
|
||||
SourceLocation &old = SemaRef.UndefinedButUsed[Var->getCanonicalDecl()];
|
||||
if (old.isInvalid())
|
||||
old = Loc;
|
||||
}
|
||||
QualType CaptureType, DeclRefType;
|
||||
SemaRef.tryCaptureVariable(Var, Loc, Sema::TryCapture_Implicit,
|
||||
|
|
|
@ -8486,15 +8486,19 @@ static GVALinkage basicGVALinkageForVariable(const ASTContext &Context,
|
|||
if (Context.isMSStaticDataMemberInlineDefinition(VD))
|
||||
return GVA_DiscardableODR;
|
||||
|
||||
GVALinkage StrongLinkage = GVA_StrongExternal;
|
||||
if (VD->isInline())
|
||||
StrongLinkage = GVA_DiscardableODR;
|
||||
|
||||
switch (VD->getTemplateSpecializationKind()) {
|
||||
case TSK_Undeclared:
|
||||
return GVA_StrongExternal;
|
||||
return StrongLinkage;
|
||||
|
||||
case TSK_ExplicitSpecialization:
|
||||
return Context.getTargetInfo().getCXXABI().isMicrosoft() &&
|
||||
VD->isStaticDataMember()
|
||||
? GVA_StrongODR
|
||||
: GVA_StrongExternal;
|
||||
: StrongLinkage;
|
||||
|
||||
case TSK_ExplicitInstantiationDefinition:
|
||||
return GVA_StrongODR;
|
||||
|
|
|
@ -1196,6 +1196,10 @@ void ASTDumper::VisitVarDecl(const VarDecl *D) {
|
|||
OS << " __module_private__";
|
||||
if (D->isNRVOVariable())
|
||||
OS << " nrvo";
|
||||
if (D->isInline())
|
||||
OS << " inline";
|
||||
if (D->isConstexpr())
|
||||
OS << " constexpr";
|
||||
if (D->hasInit()) {
|
||||
switch (D->getInitStyle()) {
|
||||
case VarDecl::CInit: OS << " cinit"; break;
|
||||
|
|
|
@ -592,12 +592,14 @@ static LinkageInfo getLVForNamespaceScopeDecl(const NamedDecl *D,
|
|||
if (Var->getStorageClass() == SC_Static)
|
||||
return LinkageInfo::internal();
|
||||
|
||||
// - a non-volatile object or reference that is explicitly declared const
|
||||
// or constexpr and neither explicitly declared extern nor previously
|
||||
// declared to have external linkage; or (there is no equivalent in C99)
|
||||
// - a non-inline, non-volatile object or reference that is explicitly
|
||||
// declared const or constexpr and neither explicitly declared extern
|
||||
// nor previously declared to have external linkage; or (there is no
|
||||
// equivalent in C99)
|
||||
if (Context.getLangOpts().CPlusPlus &&
|
||||
Var->getType().isConstQualified() &&
|
||||
!Var->getType().isVolatileQualified()) {
|
||||
!Var->getType().isVolatileQualified() &&
|
||||
!Var->isInline()) {
|
||||
const VarDecl *PrevVar = Var->getPreviousDecl();
|
||||
if (PrevVar)
|
||||
return getLVForDecl(PrevVar, computation);
|
||||
|
@ -1912,7 +1914,9 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
|
|||
// C++ [basic.def]p2:
|
||||
// A declaration is a definition unless [...] it contains the 'extern'
|
||||
// specifier or a linkage-specification and neither an initializer [...],
|
||||
// it declares a static data member in a class declaration [...].
|
||||
// it declares a non-inline static data member in a class declaration [...],
|
||||
// it declares a static data member outside a class definition and the variable
|
||||
// was defined within the class with the constexpr specifier [...],
|
||||
// C++1y [temp.expl.spec]p15:
|
||||
// An explicit specialization of a static data member or an explicit
|
||||
// specialization of a static data member template is a definition if the
|
||||
|
@ -1922,6 +1926,8 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
|
|||
// a static data member template outside the containing class?
|
||||
if (isStaticDataMember()) {
|
||||
if (isOutOfLine() &&
|
||||
!(getCanonicalDecl()->isInline() &&
|
||||
getCanonicalDecl()->isConstexpr()) &&
|
||||
(hasInit() ||
|
||||
// If the first declaration is out-of-line, this may be an
|
||||
// instantiation of an out-of-line partial specialization of a variable
|
||||
|
@ -1932,6 +1938,8 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
|
|||
TSK_ExplicitSpecialization) ||
|
||||
isa<VarTemplatePartialSpecializationDecl>(this)))
|
||||
return Definition;
|
||||
else if (!isOutOfLine() && isInline())
|
||||
return Definition;
|
||||
else
|
||||
return DeclarationOnly;
|
||||
}
|
||||
|
@ -2072,18 +2080,6 @@ bool VarDecl::isOutOfLine() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
VarDecl *VarDecl::getOutOfLineDefinition() {
|
||||
if (!isStaticDataMember())
|
||||
return nullptr;
|
||||
|
||||
for (auto RD : redecls()) {
|
||||
if (RD->getLexicalDeclContext()->isFileContext())
|
||||
return RD;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VarDecl::setInit(Expr *I) {
|
||||
if (auto *Eval = Init.dyn_cast<EvaluatedStmt *>()) {
|
||||
Eval->~EvaluatedStmt();
|
||||
|
|
|
@ -3746,6 +3746,12 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
|
|||
case Decl::Namespace:
|
||||
EmitNamespace(cast<NamespaceDecl>(D));
|
||||
break;
|
||||
case Decl::CXXRecord:
|
||||
// Emit any static data members, they may be definitions.
|
||||
for (auto *I : cast<CXXRecordDecl>(D)->decls())
|
||||
if (isa<VarDecl>(I) || isa<CXXRecordDecl>(I))
|
||||
EmitTopLevelDecl(I);
|
||||
break;
|
||||
// No code generation needed.
|
||||
case Decl::UsingShadow:
|
||||
case Decl::ClassTemplate:
|
||||
|
|
|
@ -1905,10 +1905,18 @@ void ItaniumCXXABI::EmitGuardedInit(CodeGenFunction &CGF,
|
|||
bool shouldPerformInit) {
|
||||
CGBuilderTy &Builder = CGF.Builder;
|
||||
|
||||
// We only need to use thread-safe statics for local non-TLS variables;
|
||||
// global initialization is always single-threaded.
|
||||
// Inline variables that weren't instantiated from variable templates have
|
||||
// partially-ordered initialization within their translation unit.
|
||||
bool NonTemplateInline =
|
||||
D.isInline() &&
|
||||
!isTemplateInstantiation(D.getTemplateSpecializationKind());
|
||||
|
||||
// We only need to use thread-safe statics for local non-TLS variables and
|
||||
// inline variables; other global initialization is always single-threaded
|
||||
// or (through lazy dynamic loading in multiple threads) unsequenced.
|
||||
bool threadsafe = getContext().getLangOpts().ThreadsafeStatics &&
|
||||
D.isLocalVarDecl() && !D.getTLSKind();
|
||||
(D.isLocalVarDecl() || NonTemplateInline) &&
|
||||
!D.getTLSKind();
|
||||
|
||||
// If we have a global variable with internal linkage and thread-safe statics
|
||||
// are disabled, we can just let the guard variable be of type i8.
|
||||
|
@ -1962,7 +1970,11 @@ void ItaniumCXXABI::EmitGuardedInit(CodeGenFunction &CGF,
|
|||
if (!D.isLocalVarDecl() && C &&
|
||||
CGM.getTarget().getTriple().isOSBinFormatELF()) {
|
||||
guard->setComdat(C);
|
||||
CGF.CurFn->setComdat(C);
|
||||
// An inline variable's guard function is run from the per-TU
|
||||
// initialization function, not via a dedicated global ctor function, so
|
||||
// we can't put it in a comdat.
|
||||
if (!NonTemplateInline)
|
||||
CGF.CurFn->setComdat(C);
|
||||
} else if (CGM.supportsCOMDAT() && guard->isWeakForLinker()) {
|
||||
guard->setComdat(CGM.getModule().getOrInsertComdat(guard->getName()));
|
||||
}
|
||||
|
|
|
@ -469,7 +469,8 @@ static bool ShouldRemoveFromUnused(Sema *SemaRef, const DeclaratorDecl *D) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/// Obtains a sorted list of functions that are undefined but ODR-used.
|
||||
/// Obtains a sorted list of functions and variables that are undefined but
|
||||
/// ODR-used.
|
||||
void Sema::getUndefinedButUsed(
|
||||
SmallVectorImpl<std::pair<NamedDecl *, SourceLocation> > &Undefined) {
|
||||
for (const auto &UndefinedUse : UndefinedButUsed) {
|
||||
|
@ -488,9 +489,10 @@ void Sema::getUndefinedButUsed(
|
|||
!FD->getMostRecentDecl()->isInlined())
|
||||
continue;
|
||||
} else {
|
||||
if (cast<VarDecl>(ND)->hasDefinition() != VarDecl::DeclarationOnly)
|
||||
auto *VD = cast<VarDecl>(ND);
|
||||
if (VD->hasDefinition() != VarDecl::DeclarationOnly)
|
||||
continue;
|
||||
if (ND->isExternallyVisible())
|
||||
if (VD->isExternallyVisible() && !VD->getMostRecentDecl()->isInline())
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -522,10 +524,15 @@ static void checkUndefinedButUsed(Sema &S) {
|
|||
if (!ND->isExternallyVisible()) {
|
||||
S.Diag(ND->getLocation(), diag::warn_undefined_internal)
|
||||
<< isa<VarDecl>(ND) << ND;
|
||||
} else {
|
||||
assert(cast<FunctionDecl>(ND)->getMostRecentDecl()->isInlined() &&
|
||||
} else if (auto *FD = dyn_cast<FunctionDecl>(ND)) {
|
||||
assert(FD->getMostRecentDecl()->isInlined() &&
|
||||
"used object requires definition but isn't inline or internal?");
|
||||
// FIXME: This is ill-formed; we should reject.
|
||||
S.Diag(ND->getLocation(), diag::warn_undefined_inline) << ND;
|
||||
} else {
|
||||
assert(cast<VarDecl>(ND)->getMostRecentDecl()->isInline() &&
|
||||
"used var requires definition but isn't inline or internal?");
|
||||
S.Diag(ND->getLocation(), diag::err_undefined_inline_var) << ND;
|
||||
}
|
||||
if (I->second.isValid())
|
||||
S.Diag(I->second, diag::note_used_here);
|
||||
|
|
|
@ -1457,6 +1457,9 @@ bool Sema::ShouldWarnIfUnusedFileScopedDecl(const DeclaratorDecl *D) const {
|
|||
if (VD->isStaticDataMember() &&
|
||||
VD->getTemplateSpecializationKind() == TSK_ImplicitInstantiation)
|
||||
return false;
|
||||
|
||||
if (VD->isInline() && !isMainFileLoc(*this, VD->getLocation()))
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@ -3621,6 +3624,23 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
|
|||
return New->setInvalidDecl();
|
||||
}
|
||||
|
||||
if (New->isInline() && !Old->getMostRecentDecl()->isInline()) {
|
||||
if (VarDecl *Def = Old->getDefinition()) {
|
||||
// C++1z [dcl.fcn.spec]p4:
|
||||
// If the definition of a variable appears in a translation unit before
|
||||
// its first declaration as inline, the program is ill-formed.
|
||||
Diag(New->getLocation(), diag::err_inline_decl_follows_def) << New;
|
||||
Diag(Def->getLocation(), diag::note_previous_definition);
|
||||
}
|
||||
}
|
||||
|
||||
// If this redeclaration makes the function inline, we may need to add it to
|
||||
// UndefinedButUsed.
|
||||
if (!Old->isInline() && New->isInline() && Old->isUsed(false) &&
|
||||
!Old->getDefinition() && !New->isThisDeclarationADefinition())
|
||||
UndefinedButUsed.insert(std::make_pair(Old->getCanonicalDecl(),
|
||||
SourceLocation()));
|
||||
|
||||
if (New->getTLSKind() != Old->getTLSKind()) {
|
||||
if (!Old->getTLSKind()) {
|
||||
Diag(New->getLocation(), diag::err_thread_non_thread) << New->getDeclName();
|
||||
|
@ -3652,6 +3672,12 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
|
|||
New->getDeclContext()->isDependentContext())) {
|
||||
// The previous definition is hidden, and multiple definitions are
|
||||
// permitted (in separate TUs). Form another definition of it.
|
||||
} else if (Old->isStaticDataMember() &&
|
||||
Old->getCanonicalDecl()->isInline() &&
|
||||
Old->getCanonicalDecl()->isConstexpr()) {
|
||||
// This definition won't be a definition any more once it's been merged.
|
||||
Diag(New->getLocation(),
|
||||
diag::warn_deprecated_redundant_constexpr_static_def);
|
||||
} else {
|
||||
Diag(New->getLocation(), diag::err_redefinition) << New;
|
||||
Diag(Def->getLocation(), diag::note_previous_definition);
|
||||
|
@ -3680,6 +3706,9 @@ void Sema::MergeVarDecl(VarDecl *New, LookupResult &Previous) {
|
|||
New->setAccess(Old->getAccess());
|
||||
if (NewTemplate)
|
||||
NewTemplate->setAccess(New->getAccess());
|
||||
|
||||
if (Old->isInline())
|
||||
New->setImplicitlyInline();
|
||||
}
|
||||
|
||||
/// ParsedFreeStandingDeclSpec - This method is invoked when a declspec with
|
||||
|
@ -3836,6 +3865,10 @@ Sema::ParsedFreeStandingDeclSpec(Scope *S, AccessSpecifier AS, DeclSpec &DS,
|
|||
<< DS.getSourceRange();
|
||||
}
|
||||
|
||||
if (DS.isInlineSpecified())
|
||||
Diag(DS.getInlineSpecLoc(), diag::err_inline_non_function)
|
||||
<< getLangOpts().CPlusPlus1z;
|
||||
|
||||
if (DS.isConstexprSpecified()) {
|
||||
// C++0x [dcl.constexpr]p1: constexpr can only be applied to declarations
|
||||
// and definitions of functions and variables.
|
||||
|
@ -5261,11 +5294,7 @@ NamedDecl *Sema::findLocallyScopedExternCDecl(DeclarationName Name) {
|
|||
/// does not identify a function.
|
||||
void Sema::DiagnoseFunctionSpecifiers(const DeclSpec &DS) {
|
||||
// FIXME: We should probably indicate the identifier in question to avoid
|
||||
// confusion for constructs like "inline int a(), b;"
|
||||
if (DS.isInlineSpecified())
|
||||
Diag(DS.getInlineSpecLoc(),
|
||||
diag::err_inline_non_function);
|
||||
|
||||
// confusion for constructs like "virtual int a(), b;"
|
||||
if (DS.isVirtualSpecified())
|
||||
Diag(DS.getVirtualSpecLoc(),
|
||||
diag::err_virtual_non_function);
|
||||
|
@ -5294,6 +5323,9 @@ Sema::ActOnTypedefDeclarator(Scope* S, Declarator& D, DeclContext* DC,
|
|||
|
||||
DiagnoseFunctionSpecifiers(D.getDeclSpec());
|
||||
|
||||
if (D.getDeclSpec().isInlineSpecified())
|
||||
Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function)
|
||||
<< getLangOpts().CPlusPlus1z;
|
||||
if (D.getDeclSpec().isConstexprSpecified())
|
||||
Diag(D.getDeclSpec().getConstexprSpecLoc(), diag::err_invalid_constexpr)
|
||||
<< 1;
|
||||
|
@ -6098,8 +6130,14 @@ Sema::ActOnVariableDeclarator(Scope *S, Declarator &D, DeclContext *DC,
|
|||
NewVD->setTemplateParameterListsInfo(
|
||||
Context, TemplateParamLists.drop_back(VDTemplateParamLists));
|
||||
|
||||
if (D.getDeclSpec().isConstexprSpecified())
|
||||
if (D.getDeclSpec().isConstexprSpecified()) {
|
||||
NewVD->setConstexpr(true);
|
||||
// C++1z [dcl.spec.constexpr]p1:
|
||||
// A static data member declared with the constexpr specifier is
|
||||
// implicitly an inline variable.
|
||||
if (NewVD->isStaticDataMember() && getLangOpts().CPlusPlus1z)
|
||||
NewVD->setImplicitlyInline();
|
||||
}
|
||||
|
||||
if (D.getDeclSpec().isConceptSpecified()) {
|
||||
if (VarTemplateDecl *VTD = NewVD->getDescribedVarTemplate())
|
||||
|
@ -6142,6 +6180,20 @@ Sema::ActOnVariableDeclarator(Scope *S, Declarator &D, DeclContext *DC,
|
|||
}
|
||||
}
|
||||
|
||||
if (D.getDeclSpec().isInlineSpecified()) {
|
||||
if (CurContext->isFunctionOrMethod()) {
|
||||
// 'inline' is not allowed on block scope variable declaration.
|
||||
Diag(D.getDeclSpec().getInlineSpecLoc(),
|
||||
diag::err_inline_declaration_block_scope) << Name
|
||||
<< FixItHint::CreateRemoval(D.getDeclSpec().getInlineSpecLoc());
|
||||
} else {
|
||||
Diag(D.getDeclSpec().getInlineSpecLoc(),
|
||||
getLangOpts().CPlusPlus1z ? diag::warn_cxx14_compat_inline_variable
|
||||
: diag::ext_inline_variable);
|
||||
NewVD->setInlineSpecified();
|
||||
}
|
||||
}
|
||||
|
||||
// Set the lexical context. If the declarator has a C++ scope specifier, the
|
||||
// lexical context will be different from the semantic context.
|
||||
NewVD->setLexicalDeclContext(CurContext);
|
||||
|
@ -9759,7 +9811,7 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init,
|
|||
diag::ext_aggregate_init_not_constant)
|
||||
<< Culprit->getSourceRange();
|
||||
}
|
||||
} else if (VDecl->isStaticDataMember() &&
|
||||
} else if (VDecl->isStaticDataMember() && !VDecl->isInline() &&
|
||||
VDecl->getLexicalDeclContext()->isRecord()) {
|
||||
// This is an in-class initialization for a static data member, e.g.,
|
||||
//
|
||||
|
@ -9773,8 +9825,8 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init,
|
|||
// const enumeration type, see 9.4.2.
|
||||
//
|
||||
// C++11 [class.static.data]p3:
|
||||
// If a non-volatile const static data member is of integral or
|
||||
// enumeration type, its declaration in the class definition can
|
||||
// If a non-volatile non-inline const static data member is of integral
|
||||
// or enumeration type, its declaration in the class definition can
|
||||
// specify a brace-or-equal-initializer in which every initalizer-clause
|
||||
// that is an assignment-expression is a constant expression. A static
|
||||
// data member of literal type can be declared in the class definition
|
||||
|
@ -9957,14 +10009,21 @@ void Sema::ActOnUninitializedDecl(Decl *RealDecl,
|
|||
// the definition of a variable [...] or the declaration of a static data
|
||||
// member.
|
||||
if (Var->isConstexpr() && !Var->isThisDeclarationADefinition()) {
|
||||
if (Var->isStaticDataMember())
|
||||
Diag(Var->getLocation(),
|
||||
diag::err_constexpr_static_mem_var_requires_init)
|
||||
<< Var->getDeclName();
|
||||
else
|
||||
if (Var->isStaticDataMember()) {
|
||||
// C++1z removes the relevant rule; the in-class declaration is always
|
||||
// a definition there.
|
||||
if (!getLangOpts().CPlusPlus1z) {
|
||||
Diag(Var->getLocation(),
|
||||
diag::err_constexpr_static_mem_var_requires_init)
|
||||
<< Var->getDeclName();
|
||||
Var->setInvalidDecl();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Diag(Var->getLocation(), diag::err_invalid_constexpr_var_decl);
|
||||
Var->setInvalidDecl();
|
||||
return;
|
||||
Var->setInvalidDecl();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// C++ Concepts TS [dcl.spec.concept]p1: [...] A variable template
|
||||
|
@ -10754,6 +10813,9 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D) {
|
|||
if (DeclSpec::TSCS TSCS = DS.getThreadStorageClassSpec())
|
||||
Diag(DS.getThreadStorageClassSpecLoc(), diag::err_invalid_thread)
|
||||
<< DeclSpec::getSpecifierName(TSCS);
|
||||
if (DS.isInlineSpecified())
|
||||
Diag(DS.getInlineSpecLoc(), diag::err_inline_non_function)
|
||||
<< getLangOpts().CPlusPlus1z;
|
||||
if (DS.isConstexprSpecified())
|
||||
Diag(DS.getConstexprSpecLoc(), diag::err_invalid_constexpr)
|
||||
<< 0;
|
||||
|
@ -13327,6 +13389,9 @@ FieldDecl *Sema::HandleField(Scope *S, RecordDecl *Record,
|
|||
|
||||
DiagnoseFunctionSpecifiers(D.getDeclSpec());
|
||||
|
||||
if (D.getDeclSpec().isInlineSpecified())
|
||||
Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function)
|
||||
<< getLangOpts().CPlusPlus1z;
|
||||
if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec())
|
||||
Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
|
||||
diag::err_invalid_thread)
|
||||
|
|
|
@ -13995,6 +13995,9 @@ MSPropertyDecl *Sema::HandleMSProperty(Scope *S, RecordDecl *Record,
|
|||
|
||||
DiagnoseFunctionSpecifiers(D.getDeclSpec());
|
||||
|
||||
if (D.getDeclSpec().isInlineSpecified())
|
||||
Diag(D.getDeclSpec().getInlineSpecLoc(), diag::err_inline_non_function)
|
||||
<< getLangOpts().CPlusPlus1z;
|
||||
if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec())
|
||||
Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
|
||||
diag::err_invalid_thread)
|
||||
|
|
|
@ -4612,6 +4612,9 @@ Decl *Sema::ActOnObjCExceptionDecl(Scope *S, Declarator &D) {
|
|||
Diag(DS.getStorageClassSpecLoc(), diag::err_storage_spec_on_catch_parm)
|
||||
<< DeclSpec::getSpecifierName(SCS);
|
||||
}
|
||||
if (DS.isInlineSpecified())
|
||||
Diag(DS.getInlineSpecLoc(), diag::err_inline_non_function)
|
||||
<< getLangOpts().CPlusPlus1z;
|
||||
if (DeclSpec::TSCS TSCS = D.getDeclSpec().getThreadStorageClassSpec())
|
||||
Diag(D.getDeclSpec().getThreadStorageClassSpecLoc(),
|
||||
diag::err_invalid_thread)
|
||||
|
|
|
@ -2520,8 +2520,7 @@ Sema::InstantiateClassMembers(SourceLocation PointOfInstantiation,
|
|||
// specialization and is only an explicit instantiation definition
|
||||
// of members whose definition is visible at the point of
|
||||
// instantiation.
|
||||
if (!Var->getInstantiatedFromStaticDataMember()
|
||||
->getOutOfLineDefinition())
|
||||
if (!Var->getInstantiatedFromStaticDataMember()->getDefinition())
|
||||
continue;
|
||||
|
||||
Var->setTemplateSpecializationKind(TSK, PointOfInstantiation);
|
||||
|
|
|
@ -3881,11 +3881,12 @@ void Sema::BuildVariableInstantiation(
|
|||
Context.setManglingNumber(NewVar, Context.getManglingNumber(OldVar));
|
||||
Context.setStaticLocalNumber(NewVar, Context.getStaticLocalNumber(OldVar));
|
||||
|
||||
// Delay instantiation of the initializer for variable templates until a
|
||||
// definition of the variable is needed. We need it right away if the type
|
||||
// contains 'auto'.
|
||||
// Delay instantiation of the initializer for variable templates or inline
|
||||
// static data members until a definition of the variable is needed. We need
|
||||
// it right away if the type contains 'auto'.
|
||||
if ((!isa<VarTemplateSpecializationDecl>(NewVar) &&
|
||||
!InstantiatingVarTemplate) ||
|
||||
!InstantiatingVarTemplate &&
|
||||
!(OldVar->isInline() && OldVar->isThisDeclarationADefinition())) ||
|
||||
NewVar->getType()->isUndeducedType())
|
||||
InstantiateVariableInitializer(NewVar, OldVar, TemplateArgs);
|
||||
|
||||
|
@ -3901,6 +3902,13 @@ void Sema::BuildVariableInstantiation(
|
|||
void Sema::InstantiateVariableInitializer(
|
||||
VarDecl *Var, VarDecl *OldVar,
|
||||
const MultiLevelTemplateArgumentList &TemplateArgs) {
|
||||
// We propagate the 'inline' flag with the initializer, because it
|
||||
// would otherwise imply that the variable is a definition for a
|
||||
// non-static data member.
|
||||
if (OldVar->isInlineSpecified())
|
||||
Var->setInlineSpecified();
|
||||
else if (OldVar->isInline())
|
||||
Var->setImplicitlyInline();
|
||||
|
||||
if (Var->getAnyInitializer())
|
||||
// We already have an initializer in the class.
|
||||
|
@ -4083,7 +4091,7 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
|
|||
|
||||
assert(PatternDecl && "data member was not instantiated from a template?");
|
||||
assert(PatternDecl->isStaticDataMember() && "not a static data member?");
|
||||
Def = PatternDecl->getOutOfLineDefinition();
|
||||
Def = PatternDecl->getDefinition();
|
||||
}
|
||||
|
||||
// FIXME: Check that the definition is visible before trying to instantiate
|
||||
|
@ -4181,11 +4189,16 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
|
|||
LocalInstantiationScope Local(*this);
|
||||
|
||||
VarDecl *OldVar = Var;
|
||||
if (!VarSpec)
|
||||
if (Def->isStaticDataMember() && !Def->isOutOfLine()) {
|
||||
// We're instantiating an inline static data member whose definition was
|
||||
// provided inside the class.
|
||||
// FIXME: Update record?
|
||||
InstantiateVariableInitializer(Var, Def, TemplateArgs);
|
||||
} else if (!VarSpec) {
|
||||
Var = cast_or_null<VarDecl>(SubstDecl(Def, Var->getDeclContext(),
|
||||
TemplateArgs));
|
||||
else if (Var->isStaticDataMember() &&
|
||||
Var->getLexicalDeclContext()->isRecord()) {
|
||||
} else if (Var->isStaticDataMember() &&
|
||||
Var->getLexicalDeclContext()->isRecord()) {
|
||||
// We need to instantiate the definition of a static data member template,
|
||||
// and all we have is the in-class declaration of it. Instantiate a separate
|
||||
// declaration of the definition.
|
||||
|
|
|
@ -1217,6 +1217,8 @@ ASTDeclReader::RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
|
|||
VD->NonParmVarDeclBits.NRVOVariable = Record[Idx++];
|
||||
VD->NonParmVarDeclBits.CXXForRangeDecl = Record[Idx++];
|
||||
VD->NonParmVarDeclBits.ARCPseudoStrong = Record[Idx++];
|
||||
VD->NonParmVarDeclBits.IsInline = Record[Idx++];
|
||||
VD->NonParmVarDeclBits.IsInlineSpecified = Record[Idx++];
|
||||
VD->NonParmVarDeclBits.IsConstexpr = Record[Idx++];
|
||||
VD->NonParmVarDeclBits.IsInitCapture = Record[Idx++];
|
||||
VD->NonParmVarDeclBits.PreviousDeclInSameBlockScope = Record[Idx++];
|
||||
|
|
|
@ -895,6 +895,8 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
|
|||
Record.push_back(D->isNRVOVariable());
|
||||
Record.push_back(D->isCXXForRangeDecl());
|
||||
Record.push_back(D->isARCPseudoStrong());
|
||||
Record.push_back(D->isInline());
|
||||
Record.push_back(D->isInlineSpecified());
|
||||
Record.push_back(D->isConstexpr());
|
||||
Record.push_back(D->isInitCapture());
|
||||
Record.push_back(D->isPreviousDeclInSameBlockScope());
|
||||
|
@ -941,6 +943,7 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
|
|||
D->getInit() == nullptr &&
|
||||
!isa<ParmVarDecl>(D) &&
|
||||
!isa<VarTemplateSpecializationDecl>(D) &&
|
||||
!D->isInline() &&
|
||||
!D->isConstexpr() &&
|
||||
!D->isInitCapture() &&
|
||||
!D->isPreviousDeclInSameBlockScope() &&
|
||||
|
@ -1916,6 +1919,8 @@ void ASTWriter::WriteDeclAbbrevs() {
|
|||
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isNRVOVariable
|
||||
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isCXXForRangeDecl
|
||||
Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isARCPseudoStrong
|
||||
Abv->Add(BitCodeAbbrevOp(0)); // isInline
|
||||
Abv->Add(BitCodeAbbrevOp(0)); // isInlineSpecified
|
||||
Abv->Add(BitCodeAbbrevOp(0)); // isConstexpr
|
||||
Abv->Add(BitCodeAbbrevOp(0)); // isInitCapture
|
||||
Abv->Add(BitCodeAbbrevOp(0)); // isPrevDeclInSameScope
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
// RUN: %clang_cc1 -std=c++1z -verify %s -Wdeprecated
|
||||
|
||||
namespace {
|
||||
struct A {
|
||||
static constexpr int n = 0;
|
||||
};
|
||||
const int A::n; // expected-warning {{deprecated}}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// RUN: %clang_cc1 -std=c++1z -verify %s
|
||||
|
||||
inline int f(); // expected-warning {{inline function 'f' is not defined}}
|
||||
extern inline int n; // expected-error {{inline variable 'n' is not defined}}
|
||||
|
||||
int use = f() + n; // expected-note 2{{used here}}
|
|
@ -0,0 +1,7 @@
|
|||
// RUN: %clang_cc1 -std=c++1z -verify %s
|
||||
|
||||
struct X {
|
||||
static struct A a;
|
||||
static inline struct B b; // expected-error {{incomplete type}} expected-note {{forward decl}}
|
||||
static inline struct C c = {}; // expected-error {{incomplete type}} expected-note {{forward decl}}
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++1z %s
|
||||
|
||||
struct NonLit { // expected-note 3{{no constexpr constructors}}
|
||||
NonLit();
|
||||
|
@ -6,7 +7,7 @@ struct NonLit { // expected-note 3{{no constexpr constructors}}
|
|||
|
||||
struct S {
|
||||
static constexpr int a = 0;
|
||||
static constexpr int b; // expected-error {{declaration of constexpr static data member 'b' requires an initializer}}
|
||||
static constexpr int b; // expected-error {{initializ}} expected-note 0-1{{previous}}
|
||||
|
||||
static constexpr int c = 0;
|
||||
static const int d;
|
||||
|
@ -16,19 +17,27 @@ struct S {
|
|||
static const double f = 0.0; // expected-error {{requires 'constexpr' specifier}} expected-note {{add 'constexpr'}}
|
||||
static char *const g = 0; // expected-error {{requires 'constexpr' specifier}}
|
||||
static const NonLit h = NonLit(); // expected-error {{must be initialized out of line}}
|
||||
|
||||
static inline int i; // expected-note {{previous}} expected-warning 0-1{{extension}}
|
||||
static inline int j; // expected-note {{previous}} expected-warning 0-1{{extension}}
|
||||
static constexpr int k = 0;
|
||||
};
|
||||
|
||||
constexpr int S::a;
|
||||
constexpr int S::b = 0;
|
||||
constexpr int S::b = 0; // expected-error 0-1{{redefinition}}
|
||||
|
||||
const int S::c;
|
||||
constexpr int S::d = 0;
|
||||
constexpr int S::d2;
|
||||
|
||||
int S::i; // expected-error {{redefinition}}
|
||||
int S::j; // expected-error {{redefinition}}
|
||||
const int S::k; // ok (deprecated)
|
||||
|
||||
template<typename T>
|
||||
struct U {
|
||||
static constexpr int a = 0;
|
||||
static constexpr int b; // expected-error {{declaration of constexpr static data member 'b' requires an initializer}}
|
||||
static constexpr int b; // expected-error {{initializ}}
|
||||
static constexpr NonLit h = NonLit(); // expected-error {{cannot have non-literal type 'const NonLit'}}
|
||||
static constexpr T c = T(); // expected-error {{cannot have non-literal type}}
|
||||
static const T d;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++14 %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++1z %s
|
||||
|
||||
struct notlit { // expected-note {{not literal because}}
|
||||
notlit() {}
|
||||
|
@ -26,7 +28,12 @@ void f2(constexpr int i) {} // expected-error {{function parameter cannot be con
|
|||
// non-static member
|
||||
struct s2 {
|
||||
constexpr int mi1; // expected-error {{non-static data member cannot be constexpr; did you intend to make it const?}}
|
||||
static constexpr int mi2; // expected-error {{requires an initializer}}
|
||||
static constexpr int mi2;
|
||||
#if __cplusplus <= 201402L
|
||||
// expected-error@-2 {{requires an initializer}}
|
||||
#else
|
||||
// expected-error@-4 {{default initialization of an object of const}}
|
||||
#endif
|
||||
mutable constexpr int mi3 = 3; // expected-error-re {{non-static data member cannot be constexpr{{$}}}} expected-error {{'mutable' and 'const' cannot be mixed}}
|
||||
};
|
||||
// typedef
|
||||
|
@ -71,7 +78,7 @@ struct ConstexprDtor {
|
|||
template <typename T> constexpr T ft(T t) { return t; }
|
||||
template <typename T> T gt(T t) { return t; }
|
||||
struct S {
|
||||
template<typename T> constexpr T f(); // expected-warning {{C++14}}
|
||||
template<typename T> constexpr T f(); // expected-warning 0-1{{C++14}} expected-note 0-1{{candidate}}
|
||||
template <typename T>
|
||||
T g() const; // expected-note-re {{candidate template ignored: could not match 'T (){{( __attribute__\(\(thiscall\)\))?}} const' against 'char (){{( __attribute__\(\(thiscall\)\))?}}'}}
|
||||
};
|
||||
|
@ -82,7 +89,15 @@ template <> char ft(char c) { return c; } // expected-note {{previous}}
|
|||
template <> constexpr char ft(char nl); // expected-error {{constexpr declaration of 'ft<char>' follows non-constexpr declaration}}
|
||||
template <> constexpr int gt(int nl) { return nl; }
|
||||
template <> notlit S::f() const { return notlit(); }
|
||||
template <> constexpr int S::g() { return 0; } // expected-note {{previous}} expected-warning {{C++14}}
|
||||
#if __cplusplus >= 201402L
|
||||
// expected-error@-2 {{no function template matches}}
|
||||
#endif
|
||||
template <> constexpr int S::g() { return 0; } // expected-note {{previous}}
|
||||
#if __cplusplus < 201402L
|
||||
// expected-warning@-2 {{C++14}}
|
||||
#else
|
||||
// expected-error@-4 {{does not match any declaration in 'S'}}
|
||||
#endif
|
||||
template <> int S::g() const; // expected-error {{non-constexpr declaration of 'g<int>' follows constexpr declaration}}
|
||||
// specializations can drop the 'constexpr' but not the implied 'const'.
|
||||
template <> char S::g() { return 0; } // expected-error {{no function template matches}}
|
||||
|
@ -123,3 +138,11 @@ int next(constexpr int x) { // expected-error {{function parameter cannot be con
|
|||
}
|
||||
|
||||
extern constexpr int memsz; // expected-error {{constexpr variable declaration must be a definition}}
|
||||
|
||||
namespace {
|
||||
struct A {
|
||||
static constexpr int n = 0;
|
||||
};
|
||||
// FIXME: We should diagnose this prior to C++17.
|
||||
const int &r = A::n;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
// RUN: %clang_cc1 -std=c++1z -verify %s
|
||||
|
||||
inline int f(); // ok
|
||||
inline int n; // ok
|
||||
|
||||
inline typedef int t; // expected-error {{'inline' can only appear on functions and non-local variables}}
|
||||
inline struct S {}; // expected-error {{'inline' can only appear on functions and non-local variables}}
|
||||
inline struct T {} s; // ok
|
|
@ -0,0 +1,15 @@
|
|||
// RUN: %clang_cc1 -std=c++1z -verify %s
|
||||
|
||||
void x() {
|
||||
inline int f(int); // expected-error {{inline declaration of 'f' not allowed in block scope}}
|
||||
inline int n; // expected-error {{inline declaration of 'n' not allowed in block scope}}
|
||||
static inline int m; // expected-error {{inline declaration of 'm' not allowed in block scope}}
|
||||
}
|
||||
|
||||
inline void g();
|
||||
struct X {
|
||||
inline void f();
|
||||
// FIXME: This is ill-formed per [dcl.inline]p5.
|
||||
inline void g();
|
||||
inline void h() {}
|
||||
};
|
|
@ -54,6 +54,16 @@ namespace ScopedEnum {
|
|||
int test2 = g<int>(); // expected-note {{here}}
|
||||
}
|
||||
|
||||
// - static data members
|
||||
namespace StaticDataMembers {
|
||||
template<typename T>
|
||||
struct A {
|
||||
static const int n = T::error; // expected-error {{has no members}}
|
||||
static inline int m = T::error; // expected-warning {{extension}}
|
||||
};
|
||||
A<int> ai; // expected-note {{here}}
|
||||
}
|
||||
|
||||
// And it cases the implicit instantiations of the definitions of:
|
||||
|
||||
// - unscoped member enumerations
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// RUN: %clang_cc1 -std=c++1z %s -emit-llvm -o - -triple x86_64-linux-gnu | FileCheck %s
|
||||
|
||||
struct Q {
|
||||
// CHECK: @_ZN1Q1kE = linkonce_odr constant i32 5, comdat
|
||||
static constexpr int k = 5;
|
||||
};
|
||||
const int &r = Q::k;
|
||||
|
||||
int f();
|
||||
|
||||
// const does not imply internal linkage.
|
||||
// CHECK: @external_inline = linkonce_odr constant i32 5, comdat
|
||||
inline const int external_inline = 5;
|
||||
const int &use1 = external_inline;
|
||||
|
||||
// static still does, though.
|
||||
// CHECK: @_ZL15internal_inline = internal constant i32 5
|
||||
static inline const int internal_inline = 5;
|
||||
const int &use2 = internal_inline;
|
||||
|
||||
int a = f();
|
||||
// CHECK: @b = linkonce_odr global i32 0, comdat
|
||||
// CHECK: @_ZGV1b = linkonce_odr global i64 0, comdat($b)
|
||||
inline int b = f();
|
||||
int c = f();
|
||||
|
||||
template<typename T> struct X {
|
||||
static int a;
|
||||
static inline int b;
|
||||
static int c;
|
||||
};
|
||||
// CHECK: @_ZN1XIiE1aE = linkonce_odr global i32 10
|
||||
// CHECK: @_ZN1XIiE1bE = global i32 20
|
||||
// CHECK-NOT: @_ZN1XIiE1cE
|
||||
template<> inline int X<int>::a = 10;
|
||||
int &use3 = X<int>::a;
|
||||
template<> int X<int>::b = 20;
|
||||
template<> inline int X<int>::c = 30;
|
||||
|
||||
// CHECK-LABEL: define {{.*}}global_var_init
|
||||
// CHECK: call i32 @_Z1fv
|
||||
|
||||
// CHECK-LABEL: define {{.*}}global_var_init
|
||||
// CHECK-NOT: comdat
|
||||
// CHECK-SAME: {{$}}
|
||||
// CHECK: load atomic {{.*}} acquire
|
||||
// CHECK: br
|
||||
// CHECK: __cxa_guard_acquire(i64* @_ZGV1b)
|
||||
// CHECK: br
|
||||
// CHECK: call i32 @_Z1fv
|
||||
// CHECK: __cxa_guard_release(i64* @_ZGV1b)
|
||||
|
||||
// CHECK-LABEL: define {{.*}}global_var_init
|
||||
// CHECK: call i32 @_Z1fv
|
||||
|
||||
template<typename T> inline int d = f();
|
||||
int e = d<int>;
|
||||
|
||||
// CHECK-LABEL: define {{.*}}global_var_init{{.*}}comdat
|
||||
// CHECK: _ZGV1dIiE
|
||||
// CHECK-NOT: __cxa_guard_acquire(i64* @_ZGV1b)
|
||||
// CHECK: call i32 @_Z1fv
|
||||
// CHECK-NOT: __cxa_guard_release(i64* @_ZGV1b)
|
|
@ -49,7 +49,7 @@ inline int useConst () {
|
|||
#include "inline.c"
|
||||
|
||||
// Check that we don't allow illegal uses of inline
|
||||
inline int a; // expected-error{{'inline' can only appear on functions}}
|
||||
inline int a; // expected-warning{{inline variables are a C++1z extension}}
|
||||
typedef inline int b; // expected-error{{'inline' can only appear on functions}}
|
||||
int d(inline int a); // expected-error{{'inline' can only appear on functions}}
|
||||
|
||||
|
|
|
@ -715,7 +715,7 @@ as the draft C++1z standard evolves.</p>
|
|||
<tr>
|
||||
<td>Inline variables</td>
|
||||
<td><a href="http://wg21.link/p0386r2">P0386R2</a></td>
|
||||
<td class="none" align="center">No</td>
|
||||
<td class="svn" align="center">SVN</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Structured bindings</td>
|
||||
|
|
Loading…
Reference in New Issue