Standard library modules expose the remaining macro interfaces
P2654R0 examines why import std cannot supply macro facilities and why replacing each macro requires a separate semantic decision.
Importing a library does not reproduce the preprocessor state produced by including its headers. That separation is useful for isolation, but it leaves a gap when a standard facility is itself specified as a macro. A program can import the standard library and still need a textual include for an operation it regards as part of the language's ordinary toolkit.
Alisdair Meredith's P2654R0, dated May 15, examines that gap. Its aim is to expose the same functionality without teaching named modules to export macros. The paper organizes several distinct cases for further work rather than presenting a single replacement mechanism.
I find this a useful account of the several eras that coexist in C++. Modules can provide a different compilation model while old interfaces retain their preprocessor form. A clean import statement does not make the semantic differences between those forms disappear.
Macro isolation creates a deliberate boundary
The paper describes named modules as providing isolation from unexpected macro use. Not exporting macros is part of that model. A macro interface therefore cannot be treated exactly like an exported function or type declaration.
Header units provide another approach for suitable headers, where macro effects can become available to the importer. Meredith distinguishes that path from named modules and notes that not every header has the necessary importability guarantee. A textual include remains the fallback for interfaces that cannot use that route.
This is more than choosing a shorter spelling for a dependency. A header's preprocessing behavior and a module's exported declarations participate in compilation differently. Source that relies on macro expansion has a requirement beyond gaining access to a declaration with the same name.
The practical question is therefore which facility the macro supplies. Some macros denote values. Others participate in language-like syntax or respond to build configuration. Each category places different requirements on a proposed replacement.
A constant does not replace every literal macro
P2654R0 points out that a value macro can participate in a preprocessor conditional. A constexpr variable does not automatically supply that capability. Replacing one with the other could preserve use in a C++ expression while breaking use in #if.
String-valued macros create a related issue. A string literal can participate in adjacent literal concatenation. A pointer to constant characters is a different kind of expression and cannot simply take its place in that syntax.
These examples explain why a blanket instruction to replace macros with constants is incomplete. The replacement must preserve the required operations at the phase where callers use them. A facility used during preprocessing needs an account of that phase, not only a typed value available later.
The paper mentions a possible new directive for literal-like values but leaves the supporting paper pending. That is exploratory work. It should not be presented as syntax available in C++23 or as a settled C++26 decision.
Assertions combine syntax and configuration
Meredith's companion P2884R0 explores making assert a keyword-backed operator. Its motivation includes availability with the standard library module and the preprocessor's treatment of commas in C++ expressions.
A conventional macro needs extra parentheses around some expressions containing template argument lists or braced values. The additional parentheses make the intended expression one macro argument. The compiler's understanding of C++ brackets does not automatically become the preprocessor's argument-separation rule.
A familiar form remains usable with textual inclusion:
#include <cassert>
#include <type_traits>
int main()
{
assert((std::is_same_v<int, int>));
}
The outer pair of parentheses inside the macro invocation protects the comma in the template argument list. This is existing macro usage, not an example of the proposed operator. The paper's proposed keyword would address the mismatch through language parsing.
The proposal also recognizes harder compatibility questions. NDEBUG can change whether an assertion is active, and existing source can define or undefine it. Some code relies on a disabled assertion discarding text that would otherwise fail to compile.
An operator cannot preserve that behavior merely by evaluating to nothing at runtime. The language must decide what source still has to be well-formed and how build configuration affects it. Those questions require an explicit design.
Several interfaces need different treatment
P2654R0 discusses offsetof, variadic argument facilities, and setjmp as operations with language-level behavior currently exposed through macros. It suggests keyword-based directions for some of them. It leaves errno unresolved.
That unresolved entry is informative. A survey can identify the import gap without pretending that every interface has the same straightforward replacement. Some operations carry state or object-lifetime interactions that require more than a declaration change.
The paper also discusses atomic macros and possible compiler-provided or removal-based approaches. These are different strategies for different facilities. The objective is access to the relevant functionality through the new compilation model, not preservation of every macro as a macro.
My preference is to keep those decisions separate. A literal constant, an assertion, and a nonlocal control-flow facility have different semantics. The fact that all currently use preprocessor machinery does not make them one language-design problem.
The May papers therefore describe a transition cost of standard library modules. Named-module isolation is useful, while macro interfaces still need an explicit source path or a redesigned language facility. P2654R0 makes that remaining work visible and assigns individual questions to follow-on proposals.