[clang-tools-extra] [clang-tidy] Add modernize-use-constexpr check (PR #182015)
Helmut Januschka via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 18 05:03:10 PST 2026
https://github.com/hjanuschka created https://github.com/llvm/llvm-project/pull/182015
Add new clang-tidy check that suggests replacing `const` with `constexpr` on local variables with literal types and compile-time constant initializers.
Using `constexpr` makes the compile-time nature of the value explicit and enables the compiler to enforce it.
For example:
```cpp
const int x = 42; // -> constexpr int x = 42;
const double d = 3.14; // -> constexpr double d = 3.14;
const int s = sizeof(int); // -> constexpr int s = sizeof(int);
int const y = 2 + 3 * 4; // -> int constexpr y = 2 + 3 * 4;
```
The check only triggers for local, non-static, non-volatile variables of literal type whose initializer is a C++11 constant expression. Pointer and reference types are skipped.
>From 4fb37e7ed1a8eb8da7b77724c4ca3bcda0a00ba7 Mon Sep 17 00:00:00 2001
From: Helmut Januschka <helmut at januschka.com>
Date: Wed, 18 Feb 2026 14:02:48 +0100
Subject: [PATCH] [clang-tidy] Add modernize-use-constexpr check
Suggests replacing 'const' with 'constexpr' on local variables with literal types and compile-time constant initializers.
---
.../clang-tidy/modernize/CMakeLists.txt | 1 +
.../modernize/ModernizeTidyModule.cpp | 2 +
.../modernize/UseConstexprCheck.cpp | 117 ++++++++++++++++++
.../clang-tidy/modernize/UseConstexprCheck.h | 34 +++++
clang-tools-extra/docs/ReleaseNotes.rst | 6 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../checks/modernize/use-constexpr.rst | 30 +++++
.../checkers/modernize/use-constexpr.cpp | 113 +++++++++++++++++
8 files changed, 304 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-constexpr.rst
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-constexpr.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index cc4cc7a02b594..ca4b2502d05fd 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -33,6 +33,7 @@ add_clang_library(clangTidyModernizeModule STATIC
UnaryStaticAssertCheck.cpp
UseAutoCheck.cpp
UseBoolLiteralsCheck.cpp
+ UseConstexprCheck.cpp
UseConstraintsCheck.cpp
UseDefaultMemberInitCheck.cpp
UseDesignatedInitializersCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
index fcb860d8c5298..2e62889a1c207 100644
--- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
@@ -33,6 +33,7 @@
#include "UnaryStaticAssertCheck.h"
#include "UseAutoCheck.h"
#include "UseBoolLiteralsCheck.h"
+#include "UseConstexprCheck.h"
#include "UseConstraintsCheck.h"
#include "UseDefaultMemberInitCheck.h"
#include "UseDesignatedInitializersCheck.h"
@@ -119,6 +120,7 @@ class ModernizeModule : public ClangTidyModule {
CheckFactories.registerCheck<UseAutoCheck>("modernize-use-auto");
CheckFactories.registerCheck<UseBoolLiteralsCheck>(
"modernize-use-bool-literals");
+ CheckFactories.registerCheck<UseConstexprCheck>("modernize-use-constexpr");
CheckFactories.registerCheck<UseConstraintsCheck>(
"modernize-use-constraints");
CheckFactories.registerCheck<UseDefaultMemberInitCheck>(
diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.cpp
new file mode 100644
index 0000000000000..7bf983e451bc4
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.cpp
@@ -0,0 +1,117 @@
+//===--- UseConstexprCheck.cpp - clang-tidy --------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "UseConstexprCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+void UseConstexprCheck::registerMatchers(MatchFinder *Finder) {
+ // Match local const variables with initializers that are not already
+ // constexpr, not static, and not dependent.
+ Finder->addMatcher(
+ varDecl(hasLocalStorage(), hasType(qualType(isConstQualified())),
+ hasInitializer(expr().bind("init")), unless(isConstexpr()))
+ .bind("var"),
+ this);
+}
+
+void UseConstexprCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var");
+ if (!VD)
+ return;
+
+ // Skip if the variable is static or thread_local.
+ if (VD->isStaticLocal() || VD->getTSCSpec() != TSCS_unspecified)
+ return;
+
+ // Skip volatile variables.
+ if (VD->getType().isVolatileQualified())
+ return;
+
+ // Skip dependent types (in uninstantiated templates).
+ if (VD->getType()->isDependentType())
+ return;
+
+ // The type must be a literal type for constexpr.
+ if (!VD->getType()->isLiteralType(*Result.Context))
+ return;
+
+ // Skip reference types -- constexpr references need static storage
+ // duration objects, which is too restrictive for local variables.
+ if (VD->getType()->isReferenceType())
+ return;
+
+ // Skip pointer types -- the 'const' qualifier position makes the
+ // fix-it non-trivial (e.g. 'int *const p' vs 'const int *p').
+ if (VD->getType()->isPointerType())
+ return;
+
+ const Expr *Init = VD->getInit();
+ if (!Init)
+ return;
+
+ // Skip if the initializer is value-dependent (template context).
+ if (Init->isValueDependent())
+ return;
+
+ // Check if the initializer is a C++11 constant expression.
+ if (!Init->isCXX11ConstantExpr(*Result.Context))
+ return;
+
+ // Find the 'const' token to replace with 'constexpr'.
+ const SourceLocation VarLoc = VD->getLocation();
+ const SourceLocation DeclBegin = VD->getBeginLoc();
+
+ if (DeclBegin.isInvalid() || DeclBegin.isMacroID() || VarLoc.isMacroID())
+ return;
+
+ const SourceManager &SM = *Result.SourceManager;
+ const LangOptions &LO = Result.Context->getLangOpts();
+
+ // Get source text from the start of the declaration to the variable
+ // name. This covers both 'const int x' and 'int const x'.
+ const CharSourceRange DeclRange =
+ CharSourceRange::getCharRange(DeclBegin, VarLoc);
+ const StringRef DeclText = Lexer::getSourceText(DeclRange, SM, LO);
+
+ // Find 'const' as a whole word in the declaration text.
+ SourceLocation ConstLoc;
+ size_t Pos = 0;
+ while ((Pos = DeclText.find("const", Pos)) != StringRef::npos) {
+ // Make sure it's not part of 'constexpr' or 'consteval' etc.
+ const size_t End = Pos + 5;
+ const bool WordStart =
+ (Pos == 0 || !isAlphanumeric(DeclText[Pos - 1]));
+ const bool WordEnd =
+ (End >= DeclText.size() || !isAlphanumeric(DeclText[End]));
+ if (WordStart && WordEnd) {
+ ConstLoc = DeclBegin.getLocWithOffset(Pos);
+ break;
+ }
+ Pos = End;
+ }
+
+ if (ConstLoc.isValid()) {
+ diag(VD->getLocation(), "variable %0 can be declared 'constexpr'")
+ << VD
+ << FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(ConstLoc, ConstLoc),
+ "constexpr");
+ } else {
+ // Couldn't find 'const' token; emit diagnostic without fix-it.
+ diag(VD->getLocation(), "variable %0 can be declared 'constexpr'")
+ << VD;
+ }
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.h b/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.h
new file mode 100644
index 0000000000000..994155905f803
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.h
@@ -0,0 +1,34 @@
+//===--- UseConstexprCheck.h - clang-tidy ------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTEXPRCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTEXPRCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::modernize {
+
+/// Finds ``const`` local variables with literal types and compile-time
+/// constant initializers that can be declared ``constexpr``.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-constexpr.html
+class UseConstexprCheck : public ClangTidyCheck {
+public:
+ UseConstexprCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus11;
+ }
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTEXPRCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 68bab88146241..ae53221edb2a0 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -121,6 +121,12 @@ New checks
``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with
``llvm::map_to_vector`` and ``llvm::filter_to_vector``.
+- New :doc:`modernize-use-constexpr
+ <clang-tidy/checks/modernize/use-constexpr>` check.
+
+ Finds ``const`` local variables with literal types and compile-time
+ constant initializers that can be declared ``constexpr``.
+
- New :doc:`modernize-use-string-view
<clang-tidy/checks/modernize/use-string-view>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index c475870ed7b31..85854d51da3d9 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -313,6 +313,7 @@ Clang-Tidy Checks
:doc:`modernize-unary-static-assert <modernize/unary-static-assert>`, "Yes"
:doc:`modernize-use-auto <modernize/use-auto>`, "Yes"
:doc:`modernize-use-bool-literals <modernize/use-bool-literals>`, "Yes"
+ :doc:`modernize-use-constexpr <modernize/use-constexpr>`, "Yes"
:doc:`modernize-use-constraints <modernize/use-constraints>`, "Yes"
:doc:`modernize-use-default-member-init <modernize/use-default-member-init>`, "Yes"
:doc:`modernize-use-designated-initializers <modernize/use-designated-initializers>`, "Yes"
diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constexpr.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constexpr.rst
new file mode 100644
index 0000000000000..48fdf6f921d9a
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constexpr.rst
@@ -0,0 +1,30 @@
+.. title:: clang-tidy - modernize-use-constexpr
+
+modernize-use-constexpr
+=======================
+
+Finds ``const`` local variables with literal types and compile-time
+constant initializers that can be declared ``constexpr``.
+
+Using ``constexpr`` makes the compile-time nature of the value
+explicit and enables the compiler to enforce it.
+
+For example:
+
+.. code-block:: c++
+
+ const int x = 42; // -> constexpr int x = 42;
+ const double d = 3.14; // -> constexpr double d = 3.14;
+ const bool b = true; // -> constexpr bool b = true;
+ const int s = sizeof(int); // -> constexpr int s = sizeof(int);
+
+The check only triggers when all of the following are true:
+
+- The variable is a local variable (not global or static).
+- The variable has ``const`` qualification (top-level).
+- The type is a literal type.
+- The initializer is a C++11 constant expression.
+- The variable is not already ``constexpr``.
+- The variable is not ``volatile``.
+- The variable is not a reference or pointer type.
+- The declaration is not in a macro.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constexpr.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constexpr.cpp
new file mode 100644
index 0000000000000..6e6d8fb1ebe27
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constexpr.cpp
@@ -0,0 +1,113 @@
+// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-constexpr %t
+
+// Positive: integer literal
+void test_int_literal() {
+ const int x = 42;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: variable 'x' can be declared 'constexpr' [modernize-use-constexpr]
+ // CHECK-FIXES: constexpr int x = 42;
+}
+
+// Positive: arithmetic expression of literals
+void test_arithmetic() {
+ const int y = 2 + 3 * 4;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: variable 'y' can be declared 'constexpr' [modernize-use-constexpr]
+ // CHECK-FIXES: constexpr int y = 2 + 3 * 4;
+}
+
+// Positive: bool literal
+void test_bool() {
+ const bool b = true;
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: variable 'b' can be declared 'constexpr' [modernize-use-constexpr]
+ // CHECK-FIXES: constexpr bool b = true;
+}
+
+// Positive: char literal
+void test_char() {
+ const char c = 'a';
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: variable 'c' can be declared 'constexpr' [modernize-use-constexpr]
+ // CHECK-FIXES: constexpr char c = 'a';
+}
+
+// Positive: float literal
+void test_float() {
+ const double d = 3.14;
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: variable 'd' can be declared 'constexpr' [modernize-use-constexpr]
+ // CHECK-FIXES: constexpr double d = 3.14;
+}
+
+// Positive: enum value
+enum Color { Red, Green, Blue };
+void test_enum() {
+ const Color c = Red;
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: variable 'c' can be declared 'constexpr' [modernize-use-constexpr]
+ // CHECK-FIXES: constexpr Color c = Red;
+}
+
+// Positive: sizeof expression
+void test_sizeof() {
+ const int s = sizeof(int);
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: variable 's' can be declared 'constexpr' [modernize-use-constexpr]
+ // CHECK-FIXES: constexpr int s = sizeof(int);
+}
+
+// Positive: east const style
+void test_east_const() {
+ int const x = 10;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: variable 'x' can be declared 'constexpr' [modernize-use-constexpr]
+ // CHECK-FIXES: int constexpr x = 10;
+}
+
+// Negative: already constexpr
+void test_already_constexpr() {
+ constexpr int x = 42;
+}
+
+// Negative: non-const variable
+void test_non_const() {
+ int x = 42;
+}
+
+// Negative: non-constant initializer
+int compute();
+void test_non_constant_init() {
+ const int x = compute();
+}
+
+// Negative: reference type
+void test_reference() {
+ int val = 42;
+ const int &r = val;
+}
+
+// Negative: volatile
+void test_volatile() {
+ const volatile int x = 42;
+}
+
+// Negative: static local
+void test_static() {
+ static const int x = 42;
+}
+
+// Negative: non-literal type (std::string mock)
+struct NonLiteral {
+ NonLiteral(const char *);
+ ~NonLiteral();
+};
+void test_non_literal() {
+ const NonLiteral s = "hello";
+}
+
+// Negative: pointer type (fix-it is non-trivial)
+void test_pointer() {
+ int *const p = nullptr;
+ const int *q = nullptr;
+}
+
+// Negative: global variable (not local)
+const int global_val = 42;
+
+// Negative: variable depends on function argument
+void test_depends_on_arg(int n) {
+ const int x = n + 1;
+}
More information about the cfe-commits
mailing list