Skip to content
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ New Compiler Flags
The feature has `existed <https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#running-the-instrumented-program>`_)
for a while and this is just a user facing option.

- New option ``-fimplicit-constexpr`` which implicitly makes all inlined and defined functions ``constexpr``.

Deprecated Compiler Flags
-------------------------

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -2418,6 +2418,9 @@ class FunctionDecl : public DeclaratorDecl,
bool isConstexpr() const {
return getConstexprKind() != ConstexprSpecKind::Unspecified;
}
/// Support for `-fimplicit-constexpr`
bool isConstexprOrImplicitlyCanBe(const LangOptions &LangOpts,
bool MustBeInlined = true) const;
void setConstexprKind(ConstexprSpecKind CSK) {
FunctionDeclBits.ConstexprKind = static_cast<uint64_t>(CSK);
}
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ def note_constexpr_lshift_discards : Note<"signed left shift discards bits">;
def note_constexpr_invalid_function : Note<
"%select{non-constexpr|undefined}0 %select{function|constructor}1 %2 cannot "
"be used in a constant expression">;
def note_constexpr_implicit_constexpr_must_be_inlined
: Note<"non-inline function %0 is not implicitly constexpr">;
def note_constexpr_invalid_inhctor : Note<
"constructor inherited from base class %0 cannot be used in a "
"constant expression; derived class cannot be implicitly initialized">;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ EXTENSION(matrix_types, LangOpts.MatrixTypes)
EXTENSION(matrix_types_scalar_division, true)
EXTENSION(cxx_attributes_on_using_declarations, LangOpts.CPlusPlus11)
EXTENSION(datasizeof, LangOpts.CPlusPlus)
EXTENSION(cxx_implicit_constexpr, LangOpts.ImplicitConstexpr)

FEATURE(cxx_abi_relative_vtable, LangOpts.CPlusPlus && LangOpts.RelativeCXXABIVTables)

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,7 @@ BENIGN_LANGOPT(ArrowDepth, 32, 256,
"maximum number of operator->s to follow")
BENIGN_LANGOPT(InstantiationDepth, 32, 1024,
"maximum template instantiation depth")
COMPATIBLE_LANGOPT(ImplicitConstexpr, 1, 0, "make functions implicitly 'constexpr'")
BENIGN_LANGOPT(ConstexprCallDepth, 32, 512,
"maximum constexpr call depth")
BENIGN_LANGOPT(ConstexprStepLimit, 32, 1048576,
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1991,6 +1991,12 @@ defm constant_cfstrings : BoolFOption<"constant-cfstrings",
"Disable creation of CodeFoundation-type constant strings">,
PosFlag<SetFalse>>;
def fconstant_string_class_EQ : Joined<["-"], "fconstant-string-class=">, Group<f_Group>;
def fimplicit_constexpr
: Joined<["-"], "fimplicit-constexpr">,
Group<f_Group>,
Visibility<[ClangOption, CC1Option]>,
HelpText<"All function declarations will be implicitly constexpr.">,
MarshallingInfoFlag<LangOpts<"ImplicitConstexpr">>;
def fconstexpr_depth_EQ : Joined<["-"], "fconstexpr-depth=">, Group<f_Group>,
Visibility<[ClangOption, CC1Option]>,
HelpText<"Set the maximum depth of recursive constexpr function calls">,
Expand Down
7 changes: 4 additions & 3 deletions clang/lib/AST/ByteCode/ByteCodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ void ByteCodeEmitter::compileFunc(const FunctionDecl *FuncDecl,
Func->setDefined(true);

// Lambda static invokers are a special case that we emit custom code for.
bool IsEligibleForCompilation = Func->isLambdaStaticInvoker() ||
FuncDecl->isConstexpr() ||
FuncDecl->hasAttr<MSConstexprAttr>();
bool IsEligibleForCompilation =
Func->isLambdaStaticInvoker() ||
FuncDecl->isConstexprOrImplicitlyCanBe(Ctx.getLangOpts()) ||
FuncDecl->hasAttr<MSConstexprAttr>();

// Compile the function body.
if (!IsEligibleForCompilation || !visitFunc(FuncDecl)) {
Expand Down
24 changes: 17 additions & 7 deletions clang/lib/AST/ByteCode/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,8 @@ bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F) {
return false;

if (F->isConstexpr() && F->hasBody() &&
(F->getDecl()->isConstexpr() || F->getDecl()->hasAttr<MSConstexprAttr>()))
(F->getDecl()->isConstexprOrImplicitlyCanBe(S.getLangOpts()) ||
F->getDecl()->hasAttr<MSConstexprAttr>()))
return true;

// Implicitly constexpr.
Expand All @@ -846,7 +847,7 @@ bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F) {
const auto *CD = dyn_cast<CXXConstructorDecl>(DiagDecl);
if (CD && CD->isInheritingConstructor()) {
const auto *Inherited = CD->getInheritedConstructor().getConstructor();
if (!Inherited->isConstexpr())
if (!Inherited->isConstexprOrImplicitlyCanBe(S.getLangOpts()))
DiagDecl = CD = Inherited;
}

Expand All @@ -868,19 +869,28 @@ bool CheckCallable(InterpState &S, CodePtr OpPC, const Function *F) {
// for a constant expression. It might be defined at the point we're
// actually calling it.
bool IsExtern = DiagDecl->getStorageClass() == SC_Extern;
if (!DiagDecl->isDefined() && !IsExtern && DiagDecl->isConstexpr() &&
if (!DiagDecl->isDefined() && !IsExtern &&
DiagDecl->isConstexprOrImplicitlyCanBe(S.getLangOpts()) &&
S.checkingPotentialConstantExpression())
return false;

// If the declaration is defined, declared 'constexpr' _and_ has a body,
// the below diagnostic doesn't add anything useful.
if (DiagDecl->isDefined() && DiagDecl->isConstexpr() &&
if (DiagDecl->isDefined() &&
DiagDecl->isConstexprOrImplicitlyCanBe(S.getLangOpts()) &&
DiagDecl->hasBody())
return false;

S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_invalid_function, 1)
<< DiagDecl->isConstexpr() << (bool)CD << DiagDecl;
if (S.getLangOpts().ImplicitConstexpr && !F->getDecl()->isInlined()) {
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_implicit_constexpr_must_be_inlined, 1)
<< DiagDecl;
} else {
S.FFDiag(S.Current->getLocation(OpPC),
diag::note_constexpr_invalid_function, 1)
<< DiagDecl->isConstexprOrImplicitlyCanBe(S.getLangOpts())
<< (bool)CD << DiagDecl;
}

if (DiagDecl->getDefinition())
S.Note(DiagDecl->getDefinition()->getLocation(),
Expand Down
21 changes: 21 additions & 0 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3242,6 +3242,27 @@ bool FunctionDecl::isDefined(const FunctionDecl *&Definition,
return false;
}

bool FunctionDecl::isConstexprOrImplicitlyCanBe(const LangOptions &LangOpts,
bool MustBeInlined) const {
if (isConstexpr())
return true;

if (!LangOpts.ImplicitConstexpr)
return false;

// Constexpr function in C++11 couldn't contain anything other then return
// expression. It wouldn't make sense to allow it (GCC doesn't do it neither).
if (!LangOpts.CPlusPlus14)
return false;

// Free functions must be inlined, but sometimes we want to skip this check.
// And in order to keep logic on one place, the check is here.
if (MustBeInlined)
return isInlined();

return true;
}

Stmt *FunctionDecl::getBody(const FunctionDecl *&Definition) const {
if (!hasBody(Definition))
return nullptr;
Expand Down
24 changes: 19 additions & 5 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5968,8 +5968,9 @@ static bool CheckConstexprFunction(EvalInfo &Info, SourceLocation CallLoc,

// Can we evaluate this function call?
if (Definition && Body &&
(Definition->isConstexpr() || (Info.CurrentCall->CanEvalMSConstexpr &&
Definition->hasAttr<MSConstexprAttr>())))
(Definition->isConstexprOrImplicitlyCanBe(Info.Ctx.getLangOpts()) ||
(Info.CurrentCall->CanEvalMSConstexpr &&
Definition->hasAttr<MSConstexprAttr>())))
return true;

if (Info.getLangOpts().CPlusPlus11) {
Expand All @@ -5987,12 +5988,25 @@ static bool CheckConstexprFunction(EvalInfo &Info, SourceLocation CallLoc,
// FIXME: If DiagDecl is an implicitly-declared special member function
// or an inheriting constructor, we should be much more explicit about why
// it's not constexpr.
if (CD && CD->isInheritingConstructor())
if (CD && CD->isInheritingConstructor()) {
Info.FFDiag(CallLoc, diag::note_constexpr_invalid_inhctor, 1)
<< CD->getInheritedConstructor().getConstructor()->getParent();
else

} else if (Definition && !DiagDecl->isInlined() &&
Info.Ctx.getLangOpts().ImplicitConstexpr) {
Info.FFDiag(CallLoc,
diag::note_constexpr_implicit_constexpr_must_be_inlined)
<< DiagDecl;

} else {
// Using implicit constexpr check here, so we see a missing body as main
// problem and not missing constexpr with -fimplicit-constexpr.
Info.FFDiag(CallLoc, diag::note_constexpr_invalid_function, 1)
<< DiagDecl->isConstexpr() << (bool)CD << DiagDecl;
<< DiagDecl->isConstexprOrImplicitlyCanBe(Info.Ctx.getLangOpts(),
false)
<< (bool)CD << DiagDecl;
}

Info.Note(DiagDecl->getLocation(), diag::note_declared_at);
} else {
Info.FFDiag(CallLoc, diag::note_invalid_subexpr_in_const_expr);
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6612,6 +6612,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_depth_EQ);
Args.AddLastArg(CmdArgs, options::OPT_fconstexpr_steps_EQ);

if (types::isCXX(InputType))
Args.AddLastArg(CmdArgs, options::OPT_fimplicit_constexpr);

Args.AddLastArg(CmdArgs, options::OPT_fexperimental_library);

if (Args.hasArg(options::OPT_fexperimental_new_constant_interpreter))
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Frontend/InitPreprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,9 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,

// TODO: Final number?
Builder.defineMacro("__cpp_type_aware_allocators", "202500L");

if (LangOpts.ImplicitConstexpr) // same value as GCC
Builder.defineMacro("__cpp_implicit_constexpr", "20211111");
Comment on lines +782 to +784
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't usually define macros for non-standard feature. Instead users should use __has_extension(cxx_implicit_constexpr)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is to mirror GCC's behavior, but I'm happy to remove it

}

/// InitializeOpenCLFeatureTestMacros - Define OpenCL macros based on target
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18384,16 +18384,18 @@ void Sema::MarkFunctionReferenced(SourceLocation Loc, FunctionDecl *Func,
}

if (FirstInstantiation || TSK != TSK_ImplicitInstantiation ||
Func->isConstexpr()) {
Func->isConstexprOrImplicitlyCanBe(getLangOpts())) {
if (isa<CXXRecordDecl>(Func->getDeclContext()) &&
cast<CXXRecordDecl>(Func->getDeclContext())->isLocalClass() &&
CodeSynthesisContexts.size())
PendingLocalImplicitInstantiations.push_back(
std::make_pair(Func, PointOfInstantiation));
else if (Func->isConstexpr())
else if (Func->isConstexprOrImplicitlyCanBe(getLangOpts()))
// Do not defer instantiations of constexpr functions, to avoid the
// expression evaluator needing to call back into Sema if it sees a
// call to such a function.
// (When -fimplicit-instantiation is enabled, all functions are
// implicitly constexpr)
InstantiateFunctionDefinition(PointOfInstantiation, Func);
else {
Func->setInstantiationIsPending(true);
Expand Down
117 changes: 117 additions & 0 deletions clang/test/Sema/implicit-constexpr-basic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@

// RUN: %clang_cc1 -verify=ALL_NORMAL,NORMAL14,BOTH14,ALL_PRE20,ALLNORMAL,NORMAL_PRE20,ALL -std=c++14 %s
// RUN: %clang_cc1 -verify=IMPLICIT14,IMPLICIT_PRE20,BOTH14,ALL_PRE20,ALLIMPLICIT,ALLIMPLICITOLD,ALL -fimplicit-constexpr -std=c++14 %s
// RUN: %clang_cc1 -verify=IMPLICIT14,IMPLICIT_PRE20,BOTH14,ALL_PRE20,ALLIMPLICIT,ALLIMPLICITNEW,ALL -fimplicit-constexpr -std=c++14 %s -fexperimental-new-constant-interpreter

// RUN: %clang_cc1 -verify=ALL_NORMAL,NORMAL17,BOTH17,ALL_PRE20,ALLNORMAL,NORMAL_PRE20,ALL -std=c++17 %s
// RUN: %clang_cc1 -verify=IMPLICIT17,IMPLICIT_PRE20,BOTH17,ALL_PRE20,ALLIMPLICIT,ALLIMPLICITOLD,ALL -fimplicit-constexpr -std=c++17 %s
// RUN: %clang_cc1 -verify=IMPLICIT17,IMPLICIT_PRE20,BOTH17,ALL_PRE20,ALLIMPLICIT,ALLIMPLICITNEW,ALL -fimplicit-constexpr -std=c++17 %s -fexperimental-new-constant-interpreter

// RUN: %clang_cc1 -verify=ALL_NORMAL,NORMAL20,BOTH20,ALLNORMAL,ALL -std=c++20 %s
// RUN: %clang_cc1 -verify=IMPLICIT20,BOTH20,ALLIMPLICIT,ALLIMPLICITOLD,ALL -fimplicit-constexpr -std=c++20 %s
// RUN: %clang_cc1 -verify=IMPLICIT20,BOTH20,ALLIMPLICIT,ALLIMPLICITNEW,ALL -fimplicit-constexpr -std=c++20 %s -fexperimental-new-constant-interpreter

// RUN: %clang_cc1 -verify=ALL_NORMAL,NORMAL23,BOTH23,ALLNORMAL,ALL -std=c++23 %s
// RUN: %clang_cc1 -verify=IMPLICIT23,BOTH23,ALLIMPLICIT,ALLIMPLICITOLD,ALL -fimplicit-constexpr -std=c++23 %s
// RUN: %clang_cc1 -verify=IMPLICIT23,BOTH23,ALLIMPLICIT,ALLIMPLICITNEW,ALL -fimplicit-constexpr -std=c++23 %s -fexperimental-new-constant-interpreter




// =============================================
// 1) simple uninlined function

bool noinline_fnc() {
// ALL-note@-1 {{declared here}}
return true;
}

constexpr bool result_noinline_fnc = noinline_fnc();
// ALL-error@-1 {{constexpr variable 'result_noinline_fnc' must be initialized by a constant expression}}
// ALLNORMAL-note@-2 {{non-constexpr function 'noinline_fnc' cannot be used in a constant expression}}
// ALLIMPLICIT-note@-3 {{non-inline function 'noinline_fnc' is not implicitly constexpr}}


// =============================================
// 2) simple inlined function

inline bool inline_fnc() {
// ALLNORMAL-note@-1 {{declared here}}
return true;
}

constexpr bool result_inline_fnc = inline_fnc();
// ALLNORMAL-error@-1 {{constexpr variable 'result_inline_fnc' must be initialized by a constant expression}}
// ALLNORMAL-note@-2 {{non-constexpr function 'inline_fnc' cannot be used in a constant expression}}


// =============================================
// 3) undefined uninlined function

bool noinline_undefined_fnc();
// ALL-note@-1 {{declared here}}

constexpr bool result_noinline_undefined_fnc = noinline_undefined_fnc();
// ALL-error@-1 {{constexpr variable 'result_noinline_undefined_fnc' must be initialized by a constant expression}}
// ALLNORMAL-note@-2 {{non-constexpr function 'noinline_undefined_fnc' cannot be used in a constant expression}}
// ALLIMPLICITOLD-note@-3 {{undefined function 'noinline_undefined_fnc' cannot be used in a constant expression}}
// ALLIMPLICITNEW-note@-4 {{non-inline function 'noinline_undefined_fnc' is not implicitly constexpr}}

// =============================================
// 4) undefined inline function

inline bool inline_undefined_fnc();
// ALL-note@-1 {{declared here}}

constexpr bool result_inline_undefined_fnc = inline_undefined_fnc();
// ALL-error@-1 {{constexpr variable 'result_inline_undefined_fnc' must be initialized by a constant expression}}
// ALLNORMAL-note@-2 {{non-constexpr function 'inline_undefined_fnc' cannot be used in a constant expression}}
// ALLIMPLICIT-note@-3 {{undefined function 'inline_undefined_fnc' cannot be used in a constant expression}}

// =============================================
// 5) lambda function

auto lambda = [](int x) { return x > 0; };
// NORMAL14-note@-1 {{declared here}}

constexpr bool result_lambda = lambda(10);
// NORMAL14-error@-1 {{constexpr variable 'result_lambda' must be initialized by a constant expression}}
// NORMAL14-note@-2 {{non-constexpr function 'operator()' cannot be used in a constant expression}}


// =============================================
// 6) virtual functions

struct type {
virtual bool dispatch() const noexcept {
return false;
}
};

struct child_of_type: type {
bool dispatch() const noexcept override {
// NORMAL20-note@-1 {{declared here}}
// NORMAL23-note@-2 {{declared here}}
return true;
}
};

constexpr bool result_virtual = static_cast<const type &>(child_of_type{}).dispatch();
// ALL_NORMAL-error@-1 {{constexpr variable 'result_virtual' must be initialized by a constant expression}}
// NORMAL_PRE20-note@-2 {{cannot evaluate call to virtual function in a constant expression in C++ standards before C++20}}
// IMPLICIT_PRE20-error@-3 {{constexpr variable 'result_virtual' must be initialized by a constant expression}}
// IMPLICIT_PRE20-note@-4 {{cannot evaluate call to virtual function in a constant expression in C++ standards before C++20}}
// NORMAL20-note@-5 {{non-constexpr function 'dispatch' cannot be used in a constant expression}}
// NORMAL20-note@-6 {{declared here}}
// NORMAL23-note@-7 {{non-constexpr function 'dispatch' cannot be used in a constant expression}}
// NORMAL23-note@-8 {{declared here}}


#if defined(__cpp_constexpr) && __cpp_constexpr >= 201907L
static_assert(result_virtual == true, "virtual should work");
// ALL_NORMAL-error@-1 {{static assertion expression is not an integral constant expression}}
// ALL_NORMAL-note@-2 {{initializer of 'result_virtual' is not a constant expression}}
// IMPLICIT_PRE20-note@-3 {{initializer of 'result_virtual' is not a constant expression}}
#endif


Loading
Loading