Date: Thu, 12 Feb 2026 08:46:38 +0100
On 12/02/2026 08:07, Steve Weinrich via Std-Proposals wrote:
> Many years ago we had a similar issue with threads. We wanted a means
> to keep the data that could be accessed from a thread declaratively
> distinct from the non-thread data. We ended up declaring a class to
> represent a thread and used a nested class to represent that threads
> specific data. I am wondering if a similar approach would work here
> without any language extension?
>
I had a related idea for adding "tags" to functions to help control
which functions are allowed to call which other functions. My interest
was primarily for embedded systems - so tags would be for things like
"interrupts disabled" or "in ram" (you don't want your flash programming
code accidentally calling functions that are in flash!). The same
concept would work just as well for controlling functions that can be
used with different threads, and for canon/non-canon functions.
I filed it as a feature request for gcc, as a function attribute, but
nothing came of it: <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88391>
I had not looked at marking data in any way here - maybe that would be
necessary, but it is also possible that that could be handled within the
existing type system. But I think a critical point is that if any
related checking system is added to C++, it would have to be with
user-defined tags. There is no way the standard is going to add extra
keywords to support a small niche market (lots of people are involved in
writing games, but very few work in the heart of high-performance game
engines). But the concept could be widened to be of use to a much
larger audience by making the tags user-definable.
The core of my feature request was:
My suggestions for function attributes are:
1. __attribute__((tagged("tagname")))
Mark a function as being tagged with "tagname" (or multiple tag names).
2. __attribute__((caller_tag("tagname")))
Mark a function as only being callable by a function with this tag.
3. __attribute__((callee_tag("tagname")))
Mark a function as only being allowed to call functions with this tag.
4. __attribute__((caller_notag("tagname")))
Mark a function as not being callable by a function with this tag.
5. __attribute__((callee_notag("tagname")))
Mark a function as not being allowed to call functions with this tag.
There would also need to be a statement attribute:
__attribute__((tag("tagname")))
Mark the statement as having this tag (and therefore being allowed to
call matching "caller_tag" functions, and not allowed to call matching
"caller_notag" functions).
mvh.,
David
> *From:*Std-Proposals <std-proposals-bounces_at_[hidden]> *On Behalf
> Of *mika.koivuranta via Std-Proposals
> *Sent:* Wednesday, February 11, 2026 7:49 AM
> *To:* std-proposals_at_[hidden]
> *Cc:* mika.koivuranta <mika.koivuranta_at_[hidden]>
> *Subject:* [std-proposals] Canonical State Enforcement
>
> Following proposal is in markdown format:
>
>
> # Canonical State Enforcement
>
> ## 1. Abstract
>
> This paper proposes a C++ standard for a compile-time language feature
> for expressing and enforcing *canon state* in C++. The proposal
> introduces the `canon`, `noncanon` and `input` specifiers for variables
> and functions, enabling compile-time catching of class of bugs by
> splitting the program into two separate states, which C++26 currently
> cannot express. The feature is purely opt-in, and imposes no runtime
> overhead.
>
> ## 2. Motivation
>
> A common class of errors in real-world C++ programs -- particularly in
> games, simulations, and interactive systems -- arises from unintended
> mutation of deterministic/authoritative state from outside contexts.
> These defects manifest as framerate dependent behavior, nondeterministic
> simulations, and/or desynchronization in networked or replayed systems.
>
> There are common mitigating solutions -- namely `deltatime` and fixed-
> timestep execution -- but they are purely social: They rely on no
> developer mistakenly updating or using specific state within wrong
> contexts. Contexts, which they can only know by carefully examining the
> call tree of the entire program.
>
> ```cpp
>
> void Move(float velocity)
>
> {
>
> this.position += velocity * GetDeltaTime();
>
> // oh no! GetDeltaTime() returns a value modified each frame!
>
> // Now this summation's accuracy is dependent on framerate!
>
> // Move(float Velocity) and this.position have both become noncanon!
>
> }
>
> ```
>
> In this example, the `Move(float velocity)` function itself can be used
> as a side effect of a more complicated control flow. Thanks to the
> addition of wall-clock dependent time, `Move(float Velocity)` should no
> longer be called from, for example, deterministic physics simulations.
> However, the c++ standard has no enforcement for this: Only social
> discipline can prevent this class of bugs in C++ 26 and below.
>
> Just like there is some state which should never be modified (`const`),
> there is some state which should never be modified non-canonically
> (`canon`). Underlying both of these specifiers is social discipline made
> manifest.
>
> ## 3. Definitions
>
> **Program state** refers to all memory whose contents influence program
> behavior.
>
> **Canon behavior** is a function of its prior canon state and optionally
> of explicit user inputs.
>
> **Canon state** is program state whose mutation is intended to be
> deterministic: Given identical inputs, it should evolve identically
> across executions based on only canon state itself, and independent of
> factors such as hardware and rendering speed.
>
> **Non-canon state** is program state whose mutation is allowed to depend
> on canon, non-canon and input factors.
>
> ## 4. Overview of the Approach
>
> The proposal introduces:
>
> * Three mutually exclusive specifiers of canonicality: `canon`,
> `noncanon` and `input`. These may appear in variable declarations and
> function declarations.
>
> * Canonicality as a property of variables and behaviors (contexts)
>
> * Rules governing mutation, assignment, operations, and call graphs
>
> * A default behavior for unannotated functions to preserve backwards
> compatibility
>
> ```mermaid
>
> graph TD
>
> A[[canon main]] --> B[<noncanon> frame update]
>
> B ----> C(<noncanon> render)
>
> B ----> H[<noncanon> update inputs]
>
> B ---> E(<anonymous> std::memcpy)
>
> B --> D[<noncanon> particle effects]
>
> A --> F[[<canon> tick update]]
>
> F --> D
>
> F --> E
>
> I --> H
>
> F ----> I[[<canon> apply inputs to simulation]]
>
> F ---> G[[<canon> physics simulation]]
>
> ```
>
> ## 5. Implementation
>
> ### 5.1. Program State
>
> #### 5.1.1 State Rules
>
> 1. *Canon* state **may only** be modified and assigned within *canon*
> contexts
>
> 2. *Canon* state **may only** be initialized and assigned to *canon* and
> *input* states
>
> 3. *Non-canon* state **may** be initialized and assigned to *canon*,
> *non-canon*, and *input* states
>
> 4. Any operation involving **any** *non-canon* operands always yields a
> *non-canon* result (with exception of rule 12)
>
> 5. Any operation involving **only** *canon* operands always yields a
> *canon* result (with exception of rule 12)
>
> #### 5.1.2 Declaration of State Canonicality
>
> Variable canonicality may be declared as:
>
> ```cpp
>
> float a = 1; // non-specified variables default to the canonicality of
> the context (rule 8)
>
> canon float c = 2; // canon variable
>
> noncanon float nc = 3; // noncanon variable
>
> canon noncanon float b; // error: unknown specifier
>
> canon float d = c; // allowed
>
> noncanon float e = c // allowed
>
> canon float f = nc // error: canon state cannot be assigned from a
> noncanon state (rule 2)
>
> noncanon float g = nc // allowed
>
> noncanon float h = c + nc; // allowed; yields in a noncanon result (rule 4)
>
> noncanon bool i = c > nc; // allowed; yields in a noncanon result (rule 4)
>
> canon float j = c + d; // allowed; yields in a canon result (rule 5)
>
> ```
>
> These rules impose a strict divide in the state of the program:
> Declaring `canon float c` , for example, would impose a program-wide
> restriction to the writing of `c`: For example, frame-dependent behavior
> is restricted at standard level, from accidentally modifying, the state
> of `c`.
>
> However, that is only possible if all frame-dependent contexts are
> defined as non-canon:
>
> ### 5.2. Contexts
>
> #### 5.2.1 Context Rules
>
> 6. *Non-canon*, and *canonically anonymous* contexts **may only** call
> *non-canon* and *canonically anonymous* functions
>
> 7. *Canon* contexts **may** call *canon*, *non-canon*, and *canonically
> anonymous* functions
>
> 8. Non-specified variables declared within a context inherit the
> context's canonicality. If that canonicality is *canonically anonymous*,
> they are *non-canon*
>
> 9. `switch`, `if` , `for` and `while` statement bodies' context is
> *non-canon* if their condition is *non-canon* or *input*, or if the
> condition is called from a *non-canon* context
>
> #### 5.2.2 Declaration of Context Canonicality
>
> Function canonicality may be declared as:
>
> ```cpp
>
> // note: non-specified argument types default to the global context,
> which is noncanon:
>
> // void y(float nc) canon
>
> // is equilevent to declaration of l()
>
> // canon functions:
>
> void j() canon // allowed
>
> void k(canon float c) canon; // allowed
>
> void l(noncanon float nc) canon; // allowed
>
> canon float n(canon float c) canon; // allowed
>
> noncanon float m(canon float c) canon; // allowed
>
> canon float o(noncanon float nc) canon; // allowed
>
> noncanon float p(noncanon float nc) canon; // allowed
>
> // noncanon functions:
>
> void q() noncanon // allowed
>
> void r(canon float c) noncanon; // error: modification of canon state is
> allowed only from strictly canon contexts (rule 1)
>
> void s(noncanon float nc) noncanon; // allowed
>
> canon float t(canon float c) noncanon; // error: assignment of canon
> state is allowed only from strictly canon contexts (rule 1)
>
> noncanon float u(canon float c) noncanon; // error: argument must be
> const (rule 1)
>
> canon float v(noncanon float nc) noncanon; // error: assignment of canon
> state is allowed only from strictly canon contexts (rule 1)
>
> noncanon float w(noncanon float nc) noncanon; // allowed
>
> noncanon float example = 1;
>
> void x() canon
>
> {
>
> j(); // allowed (rule 7)
>
> q(); // allowed (rule 7)
>
> if (example >= 5) // condition is non-canon; statement context is non-
> canon (rule 9)
>
> {
>
> example -= 4;
>
> j(); // error: cannot call canon functions in noncanon contexts (rule 6)
>
> q(); // allowed (rule 6)
>
> }
>
> }
>
> ```
>
> These rules establish a unidirectional flow of authority: Canon behavior
> may observe and mutate any state. Non-canon behavior may observe canon
> state and use it to calculate its own behavior, but is not allowed to
> mutate the canon state.
>
> It is good to bear in mind that calling a `canon` function is equivalent
> to advancing authoritative state. (e.g. Server, physics simulation, or
> the text on a document)
>
> Allowing non-canon contexts to call `canon` functions allows the context
> to possibly by modify canon state, resulting to it being, at least
> partly, framerate or hardware dependent. This is exactly the developer
> mistake which canonicality was proposed to prevent, which is why such
> calls aren't allowed in this proposed feature.
>
> #### 5.2.3. Canonically Anonymous Functions
>
> Functions declared without either `canon` or `noncanon` are considered
> *canonically anonymous*. Canonically anonymous is a behavior
> classification which allows helper functions with side effects tied to
> its arguments (such as `std::memcpy`) to be called from any context.
>
> Canonically anonymous functions act as helpers in any context, analogous
> to how `constexpr` functions may execute in both constant and runtime
> contexts.
>
> 10. *Canonically anonymous* functions' canonically non-specified
> arguments **may** be called with either canon or non-canon variables
>
> 11. *Canonically anonymous* functions **may** be called with non-const
> canon arguments, **only** if the context calling it is also canon
>
> In short, a compiler only needs to consider the canonicality of
> canonically anonymous functions when such function is called with canon
> or non-canon arguments. In that case, the compiler only needs to check
> whether the function's arguments are `const` or copied, and from those
> which are not, whether the given values are `canon` (or assignable to
> canon), and if they are, whether the call context is also canon, and if
> it is not, then a compilation error has occurred. This allows
> canonicality to be fully opt-in feature, and allows programs designed
> with canonicality in mind to interface with libraries which otherwise
> lack canonical considerations.
>
> #### 5.2.4. Declaration of Canonical Anonymity
>
> ```cpp
>
> // canonically anonymous functions:
>
> void c(float const &c); // allowed
>
> void b(float &c); // canonicality of this function and its argument
> depends on
>
> // whether this function is called from canon or noncanon contexts;
>
> // when called from canon context, allowed
>
> // when called from noncanon context, allowed with noncanon or const
> canon arguments, otherwise
>
> // error: argument must be const, or function must be canon (rule 11)
>
> void d(canon float &c); // error: argument must be const, or function
> must be canon (rule 11)
>
> void e(noncanon float &nc); // allowed
>
> canon float f(canon float &c); // error: assignment of canon state is
> allowed only from strictly canon contexts (rule 2)
>
> noncanon float g(canon float &c); // error: argument must be const, or
> function must be canon (rule 11)
>
> canon float h(noncanon float &nc); // error: assignment of canon value
> is allowed only from strictly canon contexts (rule 2)
>
> noncanon float i(noncanon float &nc); // allowed
>
> ```
>
> ### 5.3. Input
>
> Canon state is defined as that which isn't affected by sources outside
> canon state itself.
>
> According to this definition, user input and hardware checks would be
> understood as non-canon. If the standard is made to enforce that type of
> strict canonicality, the return types of `std::cin` and `std::fstream`
> would gain the `non-canon` specifier. However, this results in canon
> contexts with no user interaction aside from opening and closing them.
>
> In order to preserve meaningful computing in canon contexts, the user
> themselves must be understood as a non-canon, yet strictly self-
> deterministic part of the program. For the rest of the paper, *input
> state* will be defined as the part of the program which that conceptual
> user precedes over.
>
> A basic example of input is:
>
> ```cpp
>
> input bool buttonPressed;
>
> PushBox(canon float force) canon;
>
> void Tick() canon
>
> {
>
> // (imagine a non-blocking input check here)
>
> if (buttonPressed)
>
> {
>
> PushBox(200);
>
> }
>
> }
>
> ```
>
> In the above example, the user input is a simple Boolean truth; Whether
> a button is pressed or not. The canonicality of such input can be easily
> replaced with a canon.
>
> However, in the following example, such replacement cannot be easily made:
>
> ```cpp
>
> noncanon float buttonPressTime;
>
> PushBox(canon float force) canon;
>
> void Frame(deltaTime) noncanon
>
> {
>
> // (imagine a non-blocking input check here)
>
> if(buttonPressed)
>
> {
>
> buttonPressTime += deltaTime;
>
> }
>
> }
>
> void Tick() canon
>
> {
>
> PushBox(buttonPressTime); // error: function only takes canon arguments
>
> }
>
> ```
>
> In the above example, the user input is the **amount of time a key was
> pressed**. Such input's accuracy is based on input sampling rate, and so
> must be non-canon. This disqualifies it as a legal argument for
> `PushBox(canon float force) canon` function.
>
> This is the use case of the `input` specifier: It designates program
> state whose values originate from sources external to the program’s
> canon state, but which are nevertheless considered deterministic or
> authoritative.
>
> As such, input state represents a conceptual *user*, such as keyboard
> input, network messages, sensor readings, or file contents. While, for
> example, framerate is also an external effect, it is not be considered
> part of the user's intent, and therefore is not of input state.
>
> #### 5.3.1 Input Rules
>
> 12. Any operation involving **any** input operands always yields an
> input result (this precedes over rule 4)
>
> 13. *Input* state **may** be initialized and assigned to *canon*, *non-
> canon*, and *input* states
>
> These rules implicitly specify following behavior: Input state, for all
> intents and purposes, acts the same as non-canon state, with one key
> difference: Within canon behavior, canon state is not allowed to be
> assigned to non-canon state, but canon state is allowed to be assigned
> to input state, as per rules 1 and 2.
>
> In this sense, input can be understood as a way to "cast" non-canon
> state to canon state. This, sadly allows for a class of developer
> mistakes, wherein the developer adds `input` specifier overzealously to
> state which is not part of the user, or modifies input state in a way
> which is not part of the user's input.
>
> However, this class of mistakes are clearly marked with the input
> specifier and sourced by conscious disobedience to the standard, which
> -- in the author's opinion -- are an acceptable replacement to often
> hidden class of mistakes sourced from accidental disobedience to a
> specific codebase's social rules, caused canonical unsafety of the C++
> 26 standard.
>
> Note that there are no mentions of input contexts in this proposal: A
> theoretical input context would be logically equilevent to non-canon
> context.
>
> #### 5.3.2 Chart of Operations Between Canonical Types
>
> | Operand type: | Canon | Non-canon | Input
>
> |:--------|--------:|------------:|--------------:|--------------:|--------------:|
>
> | **Canon** | Canon | Non-canon| Input |
>
> | **Non-canon** | Non-canon | Non-canon | Input |
>
> | **Input** | Input | Input | Input |
>
> #### 5.2.3 Declaration of Input Canonicality
>
> ```cpp
>
> input bool buttonPressed; // allowed
>
> input float buttonPressTime; // allowed
>
> void InputFunction() input; // allowed
>
> canon float c;
>
> void Frame(float deltaTime) noncanon
>
> {
>
> buttonPressTime += deltaTime; // error: input state may not be
> modified in non-canon contexts
>
> noncanon float nc;
>
> std::cin >> nc; // allowed; noncanon state may be assigned to input state
>
> std::cin >> buttonPressed; // error: input state may not be modified in
> non-canon contexts
>
> if (buttonPressed) // condition is input; statement context is non-canon
>
> {
>
> buttonPressTime += deltaTime; // allowed
>
> nc = buttonPressTime; // allowed
>
> c = buttonPressTime; // error: cannot modify canon state in non-canon
> context
>
> }
>
> }
>
> ```
>
> ### 5.4. Classes
>
> Canonicality is not a property of class types. Instead, data members and
> member functions must be declared `canon` or `noncanon` individually. To
> declare a fully canon class, one must simply declare all of its members
> as canon.
>
> ### 5.5. Templates
>
> Template declarations inherit the canonicality of their primary
> declaration. All instantiations of a given template share the same
> canonicality.
>
> ## 6. C++ Code Example
>
> ```cpp
>
> canon float time;
>
> float frameTimer = 0.1; // global scope defaults to noncanon
>
> canon const float tickTimer = 0.2;
>
> void sideEffect(float& arg) // canonically anonymous function: Can be
> called in canon and noncanon as long as arguments arent canonically illegal
>
> {
>
> arg = 5;
>
> }
>
> template <typename T>
>
> T CalculateNewTime(T time) // canonically anonymous function: Can be
> called in canon and noncanon as long as arguments arent canonically illegal
>
> {
>
> return 0.1;
>
> }
>
> struct Foo // cannot declare canon or noncanon on structs/classes
>
> {
>
> float a = 0; // global scope defaults to noncanon
>
> noncanon float b = 1;
>
> canon float c = 2;
>
> Foo() {}
>
> Foo(float nc) : c(nc) {} // error: cannot use type "noncanon float"
> to set the value of type "canon float"
>
> Foo(canon float nc) : c(nc) {} // allowed
>
> };
>
> void Frame() noncanon // noncanon function: Can call noncanon and anon
> functions
>
> {
>
> noncanon float newTime = time; // allowed
>
> canon float* p; // allowed
>
> p = &newTime; // error: cannot modify canon state within noncanon
> behaviour
>
> noncanon float* b; // allowed
>
> b = &time; // error: cannot use type "canon float*" to set the
> value of type "noncanon float*"
>
> time = 0; // error: cannot modify canon state within noncanon behaviour
>
> Foo* foo = new Foo(); // TODO: Should default initialization of
> canon members be allowed in noncanon contexts?
>
> Foo* foo = new Foo(2); // error: cannot modify canon state "canon
> float c" in noncanon behaviour
>
> // note: literal "2" defaults to context's canonicality, which in
> this case is noncanon
>
> foo->a = foo->c + 3; // allowed
>
> foo->c = 3; // error: cannot modify canon state within noncanon
> behaviour
>
> sideEffect(time); // error: canonically anonymous function cannot
> take non-const canon arguments while called from a non-canon context
>
> sideEffect(newTime); // allowed
>
> if (newTime == time) // any operation involving any noncanon
> operands always yields a noncanon result; The condition is noncanon ->
> the statement context is noncanon
>
> {
>
> p = &newTime; // error: cannot modify canon state within
> noncanon behaviour
>
> }
>
> if (time == 0) // any operation with only canon operands always
> yields a canon result; The condition is canon, but is in non-canon
> context -> the statement context is non-canon
>
> {
>
> p = &newTime; // error: cannot modify canon state within
> noncanon behaviour
>
> }
>
> }
>
> void Tick() canon // canon function: Can call canon, noncanon and anon
> functions
>
> {
>
> noncanon float newTime = time; // allowed
>
> canon float* p; // allowed
>
> p = &newTime; // allowed
>
> noncanon float* b; // allowed
>
> b = &time; // error: cannot use type "canon float*" to set the
> value of type "noncanon float*"
>
> time = 0; // allowed
>
> Foo* foo = new Foo(); // allowed
>
> Foo* foo = new Foo(2); // allowed
>
> // note: literal "2" defaults to context's canonicality, which in
> this case is canon
>
> foo->a = foo->c + 3; // allowed
>
> foo->c = 3; // allowed
>
> sideEffect(time); // allowed
>
> sideEffect(newTime); // allowed
>
> if (newTime == time) // any operation involving any noncano
> operands always yields a noncanonresult; The condition is noncanon ->
> the statement context is noncanon
>
> {
>
> p = &newTime; // error: cannot modify canon state within
> noncanon behaviour
>
> }
>
> if (time == 0) // any operation with only canon operands always
> yields a canon result; The condition is canon -> the statment context is
> canon
>
> {
>
> p = &newTime; // allowed
>
> }
>
> }
>
> int main() canon // canon function: Can call canon, noncanon and anon
> functions
>
> {
>
> float tickTime = 0; // defaults to canon, since defined within
> canon context
>
> while (true)
>
> {
>
> time = CalculateNewTime<float>(time); // canonically anonymous
> called with non-const canon state in canon behaviour -> allowed!
>
> if (time >= frameTimer) // any operation with non-canon
> operands always yields a non-canon result; condition is non-canon ->
> statement context is non-canon
>
> {
>
> Frame(); // noncanon function call
>
> }
>
> tickTime += time; // modification of canon allowed in canon
> function
>
> while (tickTime >= tickTimer) // any operation between only
> canon operands always yields a canon result; condition is canon, and is
> in canon context -> statement context is canon
>
> {
>
> tickTime -= tickTimer;
>
> Tick(); // canon function call
>
> }
>
> }
>
> }
>
> ```
>
> ## 7. Backwards Compatibility
>
> Due to the proposed anonymous canonicality , the behavior of programs
> which do not use canonicality (`canon`, `noncanon` and `input`
> specifiers) would be unaffected. Canonicality is entirely opt-in and
> introduces no changes to the building and running of existing programs
> and libraries, except in case of macros using the names of the
> specifiers proposed in this paper.
>
> ## 8. Issues
>
> The most common argument against any standard addition is that a
> programmer could reasonably design it themselves with libraries, or with
> wrappers to another language. But in the case of canonization, this
> argument becomes an expectation for each compiler to have their own
> specification for canonicality, or for them to leave standard C++ to be
> fundamentally canonically unsafe. Both choices result in complete
> developer uncertainty, which seems an unnecessary and harsh punishment
> for simply using C++ for any low-level code.
>
> ### 8.1 Why not use types?
>
> Types can express canonized variables; `canon_float`, `noncanon_float`
> and `input_float`can express the full canonicality of a `float` type,
> even though it essentially quadruples all primitive types.
>
> However, types cannot express canonized functions: `canon_float
> function()` is not equivalent to `float function() canon`. Types also
> cannot specify canonically anonymous functions.
>
> ### 8.2 Why introduce canonicality to the whole standard?
>
> Existing approaches rely on social discipline: Conventions which prevent
> the bug, or laborious manual checks or code reviews to catch the bug.
> This proposal provides compile-time enforcement comparable in spirit to
> `const` or `static_assert`, which also exist to apply important code
> conventions to the language itself.
>
> While canonization mainly helps in physics simulations, any program with
> authoritative, deterministic state would only gain from canonization: A
> library which allows online editing of a text-file, or handling of user
> input, for example, can only safeguard their state by hiding it from the
> user completely (in a nameless namespace, or by making the state
> private). However, canonization allows for internal state to be marked,
> and its modification enforced to the exact contexts where such
> modification is allowed.
>
> Canonization also offers a solution to functions and variables which are
> intended to be deterministic: Pseudo-random number generators, timers
> and physics objects often allow modification at runtime, which runs the
> risk of a developer mistakenly modifying them non-deterministically.
> With canonization, programmers may explicitly declare functions as
> deterministic, by using the `canon` specifier.
>
> In short, canonicality should be used in any case where the **mutability
> must be limited to specifically deterministic behavior**.
>
> ### 8.3 How does canonicality affect the standard library?
>
> #### 8.3.1 Canon
>
> Canonicality is defined through the developer's intent, and so shouldn't
> be added to the standard library. Canon specifier is also opt-in, and so
> shouldn't be forced upon the developer. Furthermore, canon specifier
> breaks backwards combability in libraries it is used in.
>
> It is important to let the user be free to define the state which they
> intend enforce as canon.
>
> #### 8.3.2 Canonically Anonymous
>
> Standard helpers, such as:
>
> ```cp
>
> std::sort
>
> std::copy
>
> std::transform
>
> std::accumulate
>
> std::memcpy
>
> std::memmove
>
> ```
>
> and pure functions such as:
>
> ```cpp
>
> std::sin
>
> std::cos
>
> std::sqrt
>
> std::min
>
> std::max
>
> std::mt19937
>
> ```
>
> Should stay canonically anonymous, since their return types and side
> effects depend on call context, and such are allowed in every context.
>
> #### 8.3.4 Non-canon
>
> Non-input reading functions based on time or environment, such as:
>
> ```cpp
>
> std::chrono::system_clock::now()
>
> std::clock()
>
> std::random_device()
>
> std::getenv()
>
> ```
>
> Are definitionally non-canon, since they depend on state outside of the
> program. The functions and their return types should both be non-canon.
>
> Note that the user is free to assign any of these into canon or input
> state, choose they disagree with what makes up the conceptual user
> within their programs.
>
> #### 8.3.3 Input
>
> All of the standard library's input-reading functions, such as:
>
> ```cpp
>
> std::cin
>
> std::ifstream
>
> std::filesystem::last_write_time
>
> ```
>
> Should specify their return types as `input`. The functions themselves
> should be non-canon. Streams are not necessarily input and should stay
> with their default context-dependent canonicality.
>
> ### 8.4 Why not enforce deterministic while loops?
>
> This is possible: `while` and `for` loops can be checked at compile-time
> for determinism:
>
> ```cpp
>
> const int forever = true; // const
>
> while (forever) // non-deterministic, runs based on execution speed,
> statement context should be non-canon
>
> {
>
> //...
>
> }
>
> ```
>
> However, this is a form of the unsolvable halting problem, and therefore
> one's work on this feature will never be finished. However, some trivial
> non-halting contexts, such as bare while loops, could be enforced within
> the standard as strictly non-canon contexts. though such algorithms are
> outside the scope of this paper.
>
> Another solution is to handle `for` and `while` loops as non-canon
> contexts by default. This, however, limits the possible behaviors of
> canon behavior. Instead, allowing `canon` and `noncanon` specifiers to
> apply to `for` and `while` statements themselves gives the control fully
> to the developer, though again, such feature is outside the scope of
> this paper.
>
> ### 8.5 Open issues:
>
> * Should the specifiers `canon`, `noncanon` and `input` be attributes?
>
> * Names of the specifiers `canon`, `noncanon` and `input` should
> probably be changed for sake of brevity
>
> * Exact error wording is unfinished
>
> * Should we allow initialization of canon members within non-canon behavior?
>
> * `goto` might allow contexts to break out canon behavior, but is that
> a real problem?
>
> ## 9. Conclusion
>
> Canon state exists implicitly in all programs but is not representable
> or enforceable in current C++. This proposal introduces changes to the
> standard library, as well as a minimal compile-time, opt-in feature
> which expresses and enforces authority boundaries at compile time,
> preventing a large class of common bugs without a significant cost.
>
>
> Many years ago we had a similar issue with threads. We wanted a means
> to keep the data that could be accessed from a thread declaratively
> distinct from the non-thread data. We ended up declaring a class to
> represent a thread and used a nested class to represent that threads
> specific data. I am wondering if a similar approach would work here
> without any language extension?
>
I had a related idea for adding "tags" to functions to help control
which functions are allowed to call which other functions. My interest
was primarily for embedded systems - so tags would be for things like
"interrupts disabled" or "in ram" (you don't want your flash programming
code accidentally calling functions that are in flash!). The same
concept would work just as well for controlling functions that can be
used with different threads, and for canon/non-canon functions.
I filed it as a feature request for gcc, as a function attribute, but
nothing came of it: <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88391>
I had not looked at marking data in any way here - maybe that would be
necessary, but it is also possible that that could be handled within the
existing type system. But I think a critical point is that if any
related checking system is added to C++, it would have to be with
user-defined tags. There is no way the standard is going to add extra
keywords to support a small niche market (lots of people are involved in
writing games, but very few work in the heart of high-performance game
engines). But the concept could be widened to be of use to a much
larger audience by making the tags user-definable.
The core of my feature request was:
My suggestions for function attributes are:
1. __attribute__((tagged("tagname")))
Mark a function as being tagged with "tagname" (or multiple tag names).
2. __attribute__((caller_tag("tagname")))
Mark a function as only being callable by a function with this tag.
3. __attribute__((callee_tag("tagname")))
Mark a function as only being allowed to call functions with this tag.
4. __attribute__((caller_notag("tagname")))
Mark a function as not being callable by a function with this tag.
5. __attribute__((callee_notag("tagname")))
Mark a function as not being allowed to call functions with this tag.
There would also need to be a statement attribute:
__attribute__((tag("tagname")))
Mark the statement as having this tag (and therefore being allowed to
call matching "caller_tag" functions, and not allowed to call matching
"caller_notag" functions).
mvh.,
David
> *From:*Std-Proposals <std-proposals-bounces_at_[hidden]> *On Behalf
> Of *mika.koivuranta via Std-Proposals
> *Sent:* Wednesday, February 11, 2026 7:49 AM
> *To:* std-proposals_at_[hidden]
> *Cc:* mika.koivuranta <mika.koivuranta_at_[hidden]>
> *Subject:* [std-proposals] Canonical State Enforcement
>
> Following proposal is in markdown format:
>
>
> # Canonical State Enforcement
>
> ## 1. Abstract
>
> This paper proposes a C++ standard for a compile-time language feature
> for expressing and enforcing *canon state* in C++. The proposal
> introduces the `canon`, `noncanon` and `input` specifiers for variables
> and functions, enabling compile-time catching of class of bugs by
> splitting the program into two separate states, which C++26 currently
> cannot express. The feature is purely opt-in, and imposes no runtime
> overhead.
>
> ## 2. Motivation
>
> A common class of errors in real-world C++ programs -- particularly in
> games, simulations, and interactive systems -- arises from unintended
> mutation of deterministic/authoritative state from outside contexts.
> These defects manifest as framerate dependent behavior, nondeterministic
> simulations, and/or desynchronization in networked or replayed systems.
>
> There are common mitigating solutions -- namely `deltatime` and fixed-
> timestep execution -- but they are purely social: They rely on no
> developer mistakenly updating or using specific state within wrong
> contexts. Contexts, which they can only know by carefully examining the
> call tree of the entire program.
>
> ```cpp
>
> void Move(float velocity)
>
> {
>
> this.position += velocity * GetDeltaTime();
>
> // oh no! GetDeltaTime() returns a value modified each frame!
>
> // Now this summation's accuracy is dependent on framerate!
>
> // Move(float Velocity) and this.position have both become noncanon!
>
> }
>
> ```
>
> In this example, the `Move(float velocity)` function itself can be used
> as a side effect of a more complicated control flow. Thanks to the
> addition of wall-clock dependent time, `Move(float Velocity)` should no
> longer be called from, for example, deterministic physics simulations.
> However, the c++ standard has no enforcement for this: Only social
> discipline can prevent this class of bugs in C++ 26 and below.
>
> Just like there is some state which should never be modified (`const`),
> there is some state which should never be modified non-canonically
> (`canon`). Underlying both of these specifiers is social discipline made
> manifest.
>
> ## 3. Definitions
>
> **Program state** refers to all memory whose contents influence program
> behavior.
>
> **Canon behavior** is a function of its prior canon state and optionally
> of explicit user inputs.
>
> **Canon state** is program state whose mutation is intended to be
> deterministic: Given identical inputs, it should evolve identically
> across executions based on only canon state itself, and independent of
> factors such as hardware and rendering speed.
>
> **Non-canon state** is program state whose mutation is allowed to depend
> on canon, non-canon and input factors.
>
> ## 4. Overview of the Approach
>
> The proposal introduces:
>
> * Three mutually exclusive specifiers of canonicality: `canon`,
> `noncanon` and `input`. These may appear in variable declarations and
> function declarations.
>
> * Canonicality as a property of variables and behaviors (contexts)
>
> * Rules governing mutation, assignment, operations, and call graphs
>
> * A default behavior for unannotated functions to preserve backwards
> compatibility
>
> ```mermaid
>
> graph TD
>
> A[[canon main]] --> B[<noncanon> frame update]
>
> B ----> C(<noncanon> render)
>
> B ----> H[<noncanon> update inputs]
>
> B ---> E(<anonymous> std::memcpy)
>
> B --> D[<noncanon> particle effects]
>
> A --> F[[<canon> tick update]]
>
> F --> D
>
> F --> E
>
> I --> H
>
> F ----> I[[<canon> apply inputs to simulation]]
>
> F ---> G[[<canon> physics simulation]]
>
> ```
>
> ## 5. Implementation
>
> ### 5.1. Program State
>
> #### 5.1.1 State Rules
>
> 1. *Canon* state **may only** be modified and assigned within *canon*
> contexts
>
> 2. *Canon* state **may only** be initialized and assigned to *canon* and
> *input* states
>
> 3. *Non-canon* state **may** be initialized and assigned to *canon*,
> *non-canon*, and *input* states
>
> 4. Any operation involving **any** *non-canon* operands always yields a
> *non-canon* result (with exception of rule 12)
>
> 5. Any operation involving **only** *canon* operands always yields a
> *canon* result (with exception of rule 12)
>
> #### 5.1.2 Declaration of State Canonicality
>
> Variable canonicality may be declared as:
>
> ```cpp
>
> float a = 1; // non-specified variables default to the canonicality of
> the context (rule 8)
>
> canon float c = 2; // canon variable
>
> noncanon float nc = 3; // noncanon variable
>
> canon noncanon float b; // error: unknown specifier
>
> canon float d = c; // allowed
>
> noncanon float e = c // allowed
>
> canon float f = nc // error: canon state cannot be assigned from a
> noncanon state (rule 2)
>
> noncanon float g = nc // allowed
>
> noncanon float h = c + nc; // allowed; yields in a noncanon result (rule 4)
>
> noncanon bool i = c > nc; // allowed; yields in a noncanon result (rule 4)
>
> canon float j = c + d; // allowed; yields in a canon result (rule 5)
>
> ```
>
> These rules impose a strict divide in the state of the program:
> Declaring `canon float c` , for example, would impose a program-wide
> restriction to the writing of `c`: For example, frame-dependent behavior
> is restricted at standard level, from accidentally modifying, the state
> of `c`.
>
> However, that is only possible if all frame-dependent contexts are
> defined as non-canon:
>
> ### 5.2. Contexts
>
> #### 5.2.1 Context Rules
>
> 6. *Non-canon*, and *canonically anonymous* contexts **may only** call
> *non-canon* and *canonically anonymous* functions
>
> 7. *Canon* contexts **may** call *canon*, *non-canon*, and *canonically
> anonymous* functions
>
> 8. Non-specified variables declared within a context inherit the
> context's canonicality. If that canonicality is *canonically anonymous*,
> they are *non-canon*
>
> 9. `switch`, `if` , `for` and `while` statement bodies' context is
> *non-canon* if their condition is *non-canon* or *input*, or if the
> condition is called from a *non-canon* context
>
> #### 5.2.2 Declaration of Context Canonicality
>
> Function canonicality may be declared as:
>
> ```cpp
>
> // note: non-specified argument types default to the global context,
> which is noncanon:
>
> // void y(float nc) canon
>
> // is equilevent to declaration of l()
>
> // canon functions:
>
> void j() canon // allowed
>
> void k(canon float c) canon; // allowed
>
> void l(noncanon float nc) canon; // allowed
>
> canon float n(canon float c) canon; // allowed
>
> noncanon float m(canon float c) canon; // allowed
>
> canon float o(noncanon float nc) canon; // allowed
>
> noncanon float p(noncanon float nc) canon; // allowed
>
> // noncanon functions:
>
> void q() noncanon // allowed
>
> void r(canon float c) noncanon; // error: modification of canon state is
> allowed only from strictly canon contexts (rule 1)
>
> void s(noncanon float nc) noncanon; // allowed
>
> canon float t(canon float c) noncanon; // error: assignment of canon
> state is allowed only from strictly canon contexts (rule 1)
>
> noncanon float u(canon float c) noncanon; // error: argument must be
> const (rule 1)
>
> canon float v(noncanon float nc) noncanon; // error: assignment of canon
> state is allowed only from strictly canon contexts (rule 1)
>
> noncanon float w(noncanon float nc) noncanon; // allowed
>
> noncanon float example = 1;
>
> void x() canon
>
> {
>
> j(); // allowed (rule 7)
>
> q(); // allowed (rule 7)
>
> if (example >= 5) // condition is non-canon; statement context is non-
> canon (rule 9)
>
> {
>
> example -= 4;
>
> j(); // error: cannot call canon functions in noncanon contexts (rule 6)
>
> q(); // allowed (rule 6)
>
> }
>
> }
>
> ```
>
> These rules establish a unidirectional flow of authority: Canon behavior
> may observe and mutate any state. Non-canon behavior may observe canon
> state and use it to calculate its own behavior, but is not allowed to
> mutate the canon state.
>
> It is good to bear in mind that calling a `canon` function is equivalent
> to advancing authoritative state. (e.g. Server, physics simulation, or
> the text on a document)
>
> Allowing non-canon contexts to call `canon` functions allows the context
> to possibly by modify canon state, resulting to it being, at least
> partly, framerate or hardware dependent. This is exactly the developer
> mistake which canonicality was proposed to prevent, which is why such
> calls aren't allowed in this proposed feature.
>
> #### 5.2.3. Canonically Anonymous Functions
>
> Functions declared without either `canon` or `noncanon` are considered
> *canonically anonymous*. Canonically anonymous is a behavior
> classification which allows helper functions with side effects tied to
> its arguments (such as `std::memcpy`) to be called from any context.
>
> Canonically anonymous functions act as helpers in any context, analogous
> to how `constexpr` functions may execute in both constant and runtime
> contexts.
>
> 10. *Canonically anonymous* functions' canonically non-specified
> arguments **may** be called with either canon or non-canon variables
>
> 11. *Canonically anonymous* functions **may** be called with non-const
> canon arguments, **only** if the context calling it is also canon
>
> In short, a compiler only needs to consider the canonicality of
> canonically anonymous functions when such function is called with canon
> or non-canon arguments. In that case, the compiler only needs to check
> whether the function's arguments are `const` or copied, and from those
> which are not, whether the given values are `canon` (or assignable to
> canon), and if they are, whether the call context is also canon, and if
> it is not, then a compilation error has occurred. This allows
> canonicality to be fully opt-in feature, and allows programs designed
> with canonicality in mind to interface with libraries which otherwise
> lack canonical considerations.
>
> #### 5.2.4. Declaration of Canonical Anonymity
>
> ```cpp
>
> // canonically anonymous functions:
>
> void c(float const &c); // allowed
>
> void b(float &c); // canonicality of this function and its argument
> depends on
>
> // whether this function is called from canon or noncanon contexts;
>
> // when called from canon context, allowed
>
> // when called from noncanon context, allowed with noncanon or const
> canon arguments, otherwise
>
> // error: argument must be const, or function must be canon (rule 11)
>
> void d(canon float &c); // error: argument must be const, or function
> must be canon (rule 11)
>
> void e(noncanon float &nc); // allowed
>
> canon float f(canon float &c); // error: assignment of canon state is
> allowed only from strictly canon contexts (rule 2)
>
> noncanon float g(canon float &c); // error: argument must be const, or
> function must be canon (rule 11)
>
> canon float h(noncanon float &nc); // error: assignment of canon value
> is allowed only from strictly canon contexts (rule 2)
>
> noncanon float i(noncanon float &nc); // allowed
>
> ```
>
> ### 5.3. Input
>
> Canon state is defined as that which isn't affected by sources outside
> canon state itself.
>
> According to this definition, user input and hardware checks would be
> understood as non-canon. If the standard is made to enforce that type of
> strict canonicality, the return types of `std::cin` and `std::fstream`
> would gain the `non-canon` specifier. However, this results in canon
> contexts with no user interaction aside from opening and closing them.
>
> In order to preserve meaningful computing in canon contexts, the user
> themselves must be understood as a non-canon, yet strictly self-
> deterministic part of the program. For the rest of the paper, *input
> state* will be defined as the part of the program which that conceptual
> user precedes over.
>
> A basic example of input is:
>
> ```cpp
>
> input bool buttonPressed;
>
> PushBox(canon float force) canon;
>
> void Tick() canon
>
> {
>
> // (imagine a non-blocking input check here)
>
> if (buttonPressed)
>
> {
>
> PushBox(200);
>
> }
>
> }
>
> ```
>
> In the above example, the user input is a simple Boolean truth; Whether
> a button is pressed or not. The canonicality of such input can be easily
> replaced with a canon.
>
> However, in the following example, such replacement cannot be easily made:
>
> ```cpp
>
> noncanon float buttonPressTime;
>
> PushBox(canon float force) canon;
>
> void Frame(deltaTime) noncanon
>
> {
>
> // (imagine a non-blocking input check here)
>
> if(buttonPressed)
>
> {
>
> buttonPressTime += deltaTime;
>
> }
>
> }
>
> void Tick() canon
>
> {
>
> PushBox(buttonPressTime); // error: function only takes canon arguments
>
> }
>
> ```
>
> In the above example, the user input is the **amount of time a key was
> pressed**. Such input's accuracy is based on input sampling rate, and so
> must be non-canon. This disqualifies it as a legal argument for
> `PushBox(canon float force) canon` function.
>
> This is the use case of the `input` specifier: It designates program
> state whose values originate from sources external to the program’s
> canon state, but which are nevertheless considered deterministic or
> authoritative.
>
> As such, input state represents a conceptual *user*, such as keyboard
> input, network messages, sensor readings, or file contents. While, for
> example, framerate is also an external effect, it is not be considered
> part of the user's intent, and therefore is not of input state.
>
> #### 5.3.1 Input Rules
>
> 12. Any operation involving **any** input operands always yields an
> input result (this precedes over rule 4)
>
> 13. *Input* state **may** be initialized and assigned to *canon*, *non-
> canon*, and *input* states
>
> These rules implicitly specify following behavior: Input state, for all
> intents and purposes, acts the same as non-canon state, with one key
> difference: Within canon behavior, canon state is not allowed to be
> assigned to non-canon state, but canon state is allowed to be assigned
> to input state, as per rules 1 and 2.
>
> In this sense, input can be understood as a way to "cast" non-canon
> state to canon state. This, sadly allows for a class of developer
> mistakes, wherein the developer adds `input` specifier overzealously to
> state which is not part of the user, or modifies input state in a way
> which is not part of the user's input.
>
> However, this class of mistakes are clearly marked with the input
> specifier and sourced by conscious disobedience to the standard, which
> -- in the author's opinion -- are an acceptable replacement to often
> hidden class of mistakes sourced from accidental disobedience to a
> specific codebase's social rules, caused canonical unsafety of the C++
> 26 standard.
>
> Note that there are no mentions of input contexts in this proposal: A
> theoretical input context would be logically equilevent to non-canon
> context.
>
> #### 5.3.2 Chart of Operations Between Canonical Types
>
> | Operand type: | Canon | Non-canon | Input
>
> |:--------|--------:|------------:|--------------:|--------------:|--------------:|
>
> | **Canon** | Canon | Non-canon| Input |
>
> | **Non-canon** | Non-canon | Non-canon | Input |
>
> | **Input** | Input | Input | Input |
>
> #### 5.2.3 Declaration of Input Canonicality
>
> ```cpp
>
> input bool buttonPressed; // allowed
>
> input float buttonPressTime; // allowed
>
> void InputFunction() input; // allowed
>
> canon float c;
>
> void Frame(float deltaTime) noncanon
>
> {
>
> buttonPressTime += deltaTime; // error: input state may not be
> modified in non-canon contexts
>
> noncanon float nc;
>
> std::cin >> nc; // allowed; noncanon state may be assigned to input state
>
> std::cin >> buttonPressed; // error: input state may not be modified in
> non-canon contexts
>
> if (buttonPressed) // condition is input; statement context is non-canon
>
> {
>
> buttonPressTime += deltaTime; // allowed
>
> nc = buttonPressTime; // allowed
>
> c = buttonPressTime; // error: cannot modify canon state in non-canon
> context
>
> }
>
> }
>
> ```
>
> ### 5.4. Classes
>
> Canonicality is not a property of class types. Instead, data members and
> member functions must be declared `canon` or `noncanon` individually. To
> declare a fully canon class, one must simply declare all of its members
> as canon.
>
> ### 5.5. Templates
>
> Template declarations inherit the canonicality of their primary
> declaration. All instantiations of a given template share the same
> canonicality.
>
> ## 6. C++ Code Example
>
> ```cpp
>
> canon float time;
>
> float frameTimer = 0.1; // global scope defaults to noncanon
>
> canon const float tickTimer = 0.2;
>
> void sideEffect(float& arg) // canonically anonymous function: Can be
> called in canon and noncanon as long as arguments arent canonically illegal
>
> {
>
> arg = 5;
>
> }
>
> template <typename T>
>
> T CalculateNewTime(T time) // canonically anonymous function: Can be
> called in canon and noncanon as long as arguments arent canonically illegal
>
> {
>
> return 0.1;
>
> }
>
> struct Foo // cannot declare canon or noncanon on structs/classes
>
> {
>
> float a = 0; // global scope defaults to noncanon
>
> noncanon float b = 1;
>
> canon float c = 2;
>
> Foo() {}
>
> Foo(float nc) : c(nc) {} // error: cannot use type "noncanon float"
> to set the value of type "canon float"
>
> Foo(canon float nc) : c(nc) {} // allowed
>
> };
>
> void Frame() noncanon // noncanon function: Can call noncanon and anon
> functions
>
> {
>
> noncanon float newTime = time; // allowed
>
> canon float* p; // allowed
>
> p = &newTime; // error: cannot modify canon state within noncanon
> behaviour
>
> noncanon float* b; // allowed
>
> b = &time; // error: cannot use type "canon float*" to set the
> value of type "noncanon float*"
>
> time = 0; // error: cannot modify canon state within noncanon behaviour
>
> Foo* foo = new Foo(); // TODO: Should default initialization of
> canon members be allowed in noncanon contexts?
>
> Foo* foo = new Foo(2); // error: cannot modify canon state "canon
> float c" in noncanon behaviour
>
> // note: literal "2" defaults to context's canonicality, which in
> this case is noncanon
>
> foo->a = foo->c + 3; // allowed
>
> foo->c = 3; // error: cannot modify canon state within noncanon
> behaviour
>
> sideEffect(time); // error: canonically anonymous function cannot
> take non-const canon arguments while called from a non-canon context
>
> sideEffect(newTime); // allowed
>
> if (newTime == time) // any operation involving any noncanon
> operands always yields a noncanon result; The condition is noncanon ->
> the statement context is noncanon
>
> {
>
> p = &newTime; // error: cannot modify canon state within
> noncanon behaviour
>
> }
>
> if (time == 0) // any operation with only canon operands always
> yields a canon result; The condition is canon, but is in non-canon
> context -> the statement context is non-canon
>
> {
>
> p = &newTime; // error: cannot modify canon state within
> noncanon behaviour
>
> }
>
> }
>
> void Tick() canon // canon function: Can call canon, noncanon and anon
> functions
>
> {
>
> noncanon float newTime = time; // allowed
>
> canon float* p; // allowed
>
> p = &newTime; // allowed
>
> noncanon float* b; // allowed
>
> b = &time; // error: cannot use type "canon float*" to set the
> value of type "noncanon float*"
>
> time = 0; // allowed
>
> Foo* foo = new Foo(); // allowed
>
> Foo* foo = new Foo(2); // allowed
>
> // note: literal "2" defaults to context's canonicality, which in
> this case is canon
>
> foo->a = foo->c + 3; // allowed
>
> foo->c = 3; // allowed
>
> sideEffect(time); // allowed
>
> sideEffect(newTime); // allowed
>
> if (newTime == time) // any operation involving any noncano
> operands always yields a noncanonresult; The condition is noncanon ->
> the statement context is noncanon
>
> {
>
> p = &newTime; // error: cannot modify canon state within
> noncanon behaviour
>
> }
>
> if (time == 0) // any operation with only canon operands always
> yields a canon result; The condition is canon -> the statment context is
> canon
>
> {
>
> p = &newTime; // allowed
>
> }
>
> }
>
> int main() canon // canon function: Can call canon, noncanon and anon
> functions
>
> {
>
> float tickTime = 0; // defaults to canon, since defined within
> canon context
>
> while (true)
>
> {
>
> time = CalculateNewTime<float>(time); // canonically anonymous
> called with non-const canon state in canon behaviour -> allowed!
>
> if (time >= frameTimer) // any operation with non-canon
> operands always yields a non-canon result; condition is non-canon ->
> statement context is non-canon
>
> {
>
> Frame(); // noncanon function call
>
> }
>
> tickTime += time; // modification of canon allowed in canon
> function
>
> while (tickTime >= tickTimer) // any operation between only
> canon operands always yields a canon result; condition is canon, and is
> in canon context -> statement context is canon
>
> {
>
> tickTime -= tickTimer;
>
> Tick(); // canon function call
>
> }
>
> }
>
> }
>
> ```
>
> ## 7. Backwards Compatibility
>
> Due to the proposed anonymous canonicality , the behavior of programs
> which do not use canonicality (`canon`, `noncanon` and `input`
> specifiers) would be unaffected. Canonicality is entirely opt-in and
> introduces no changes to the building and running of existing programs
> and libraries, except in case of macros using the names of the
> specifiers proposed in this paper.
>
> ## 8. Issues
>
> The most common argument against any standard addition is that a
> programmer could reasonably design it themselves with libraries, or with
> wrappers to another language. But in the case of canonization, this
> argument becomes an expectation for each compiler to have their own
> specification for canonicality, or for them to leave standard C++ to be
> fundamentally canonically unsafe. Both choices result in complete
> developer uncertainty, which seems an unnecessary and harsh punishment
> for simply using C++ for any low-level code.
>
> ### 8.1 Why not use types?
>
> Types can express canonized variables; `canon_float`, `noncanon_float`
> and `input_float`can express the full canonicality of a `float` type,
> even though it essentially quadruples all primitive types.
>
> However, types cannot express canonized functions: `canon_float
> function()` is not equivalent to `float function() canon`. Types also
> cannot specify canonically anonymous functions.
>
> ### 8.2 Why introduce canonicality to the whole standard?
>
> Existing approaches rely on social discipline: Conventions which prevent
> the bug, or laborious manual checks or code reviews to catch the bug.
> This proposal provides compile-time enforcement comparable in spirit to
> `const` or `static_assert`, which also exist to apply important code
> conventions to the language itself.
>
> While canonization mainly helps in physics simulations, any program with
> authoritative, deterministic state would only gain from canonization: A
> library which allows online editing of a text-file, or handling of user
> input, for example, can only safeguard their state by hiding it from the
> user completely (in a nameless namespace, or by making the state
> private). However, canonization allows for internal state to be marked,
> and its modification enforced to the exact contexts where such
> modification is allowed.
>
> Canonization also offers a solution to functions and variables which are
> intended to be deterministic: Pseudo-random number generators, timers
> and physics objects often allow modification at runtime, which runs the
> risk of a developer mistakenly modifying them non-deterministically.
> With canonization, programmers may explicitly declare functions as
> deterministic, by using the `canon` specifier.
>
> In short, canonicality should be used in any case where the **mutability
> must be limited to specifically deterministic behavior**.
>
> ### 8.3 How does canonicality affect the standard library?
>
> #### 8.3.1 Canon
>
> Canonicality is defined through the developer's intent, and so shouldn't
> be added to the standard library. Canon specifier is also opt-in, and so
> shouldn't be forced upon the developer. Furthermore, canon specifier
> breaks backwards combability in libraries it is used in.
>
> It is important to let the user be free to define the state which they
> intend enforce as canon.
>
> #### 8.3.2 Canonically Anonymous
>
> Standard helpers, such as:
>
> ```cp
>
> std::sort
>
> std::copy
>
> std::transform
>
> std::accumulate
>
> std::memcpy
>
> std::memmove
>
> ```
>
> and pure functions such as:
>
> ```cpp
>
> std::sin
>
> std::cos
>
> std::sqrt
>
> std::min
>
> std::max
>
> std::mt19937
>
> ```
>
> Should stay canonically anonymous, since their return types and side
> effects depend on call context, and such are allowed in every context.
>
> #### 8.3.4 Non-canon
>
> Non-input reading functions based on time or environment, such as:
>
> ```cpp
>
> std::chrono::system_clock::now()
>
> std::clock()
>
> std::random_device()
>
> std::getenv()
>
> ```
>
> Are definitionally non-canon, since they depend on state outside of the
> program. The functions and their return types should both be non-canon.
>
> Note that the user is free to assign any of these into canon or input
> state, choose they disagree with what makes up the conceptual user
> within their programs.
>
> #### 8.3.3 Input
>
> All of the standard library's input-reading functions, such as:
>
> ```cpp
>
> std::cin
>
> std::ifstream
>
> std::filesystem::last_write_time
>
> ```
>
> Should specify their return types as `input`. The functions themselves
> should be non-canon. Streams are not necessarily input and should stay
> with their default context-dependent canonicality.
>
> ### 8.4 Why not enforce deterministic while loops?
>
> This is possible: `while` and `for` loops can be checked at compile-time
> for determinism:
>
> ```cpp
>
> const int forever = true; // const
>
> while (forever) // non-deterministic, runs based on execution speed,
> statement context should be non-canon
>
> {
>
> //...
>
> }
>
> ```
>
> However, this is a form of the unsolvable halting problem, and therefore
> one's work on this feature will never be finished. However, some trivial
> non-halting contexts, such as bare while loops, could be enforced within
> the standard as strictly non-canon contexts. though such algorithms are
> outside the scope of this paper.
>
> Another solution is to handle `for` and `while` loops as non-canon
> contexts by default. This, however, limits the possible behaviors of
> canon behavior. Instead, allowing `canon` and `noncanon` specifiers to
> apply to `for` and `while` statements themselves gives the control fully
> to the developer, though again, such feature is outside the scope of
> this paper.
>
> ### 8.5 Open issues:
>
> * Should the specifiers `canon`, `noncanon` and `input` be attributes?
>
> * Names of the specifiers `canon`, `noncanon` and `input` should
> probably be changed for sake of brevity
>
> * Exact error wording is unfinished
>
> * Should we allow initialization of canon members within non-canon behavior?
>
> * `goto` might allow contexts to break out canon behavior, but is that
> a real problem?
>
> ## 9. Conclusion
>
> Canon state exists implicitly in all programs but is not representable
> or enforceable in current C++. This proposal introduces changes to the
> standard library, as well as a minimal compile-time, opt-in feature
> which expresses and enforces authority boundaries at compile time,
> preventing a large class of common bugs without a significant cost.
>
>
Received on 2026-02-12 07:46:49
