Date: Sun, 11 Jan 2026 10:07:21 -0500
Arthur,
First, thanks for your extremely insightful and thought provoking replies.
> I'd say that "the standard currently makes those assertions" is the claim; in the paper as presented you didn't give the evidence for that claim — which would be to quote (or at least to identify) the passages you're intending to introduce as evidence, and showing the logical links that lead you from the evidence to the claim (except optionally in cases where those links are super obvious).
Ah. OK. Cite chapter and verse. Thanks.
> No, that claim I can show to be incorrect. Here `Foo` is 100% definitely an aggregate...
You're right. That's what I get for typing while my wife is calling me upstairs. I used `struct` when I meant `class`, and aggregates have different rules. Rookie mistake.
But mistakes (also known as experience) can lead you somewhere interesting.
I decided to stop being lazy and actually put my example through a compiler. Novel concept, I know. So I fired up Compiler Explorer, wrote a proper test case to demonstrate my example, and here it is <https://godbolt.org/z/4hx9hr1xv>. Point made.
However, what's in that link isn't what I tested at first. See, I made yet another mistake. Have I ever told you about the two things every human is really, really good at?
Anyway, Compiler Explorer has this annoying little feature. Is has two sections with the identical header "CLANG X86-64" and I happened to pick the one that only has official releases.
Initially, I picked clang 21. Well, that same code I posted in the link above has different behavior if you change the clang version to anything prior to trunk.
Look here <https://godbolt.org/z/9cshPonz9> for something a bit different.
Clang 21 has a very different implementation of `__builtin_is_implicit_lifetime` than trunk, especially regarding what qualifies as a "trivial eligible constructor."
This result only magnifies your earlier claims about wording, and sent chasing some rabbits.
I'll get to all of this, but this is not a short email.
If I had a blog, I'd just write a cool (or not so cool) article, and post the link.
Instead, you get some more markdown.
First, thanks for your extremely insightful and thought provoking replies.
> I'd say that "the standard currently makes those assertions" is the claim; in the paper as presented you didn't give the evidence for that claim — which would be to quote (or at least to identify) the passages you're intending to introduce as evidence, and showing the logical links that lead you from the evidence to the claim (except optionally in cases where those links are super obvious).
Ah. OK. Cite chapter and verse. Thanks.
> No, that claim I can show to be incorrect. Here `Foo` is 100% definitely an aggregate...
You're right. That's what I get for typing while my wife is calling me upstairs. I used `struct` when I meant `class`, and aggregates have different rules. Rookie mistake.
But mistakes (also known as experience) can lead you somewhere interesting.
I decided to stop being lazy and actually put my example through a compiler. Novel concept, I know. So I fired up Compiler Explorer, wrote a proper test case to demonstrate my example, and here it is <https://godbolt.org/z/4hx9hr1xv>. Point made.
However, what's in that link isn't what I tested at first. See, I made yet another mistake. Have I ever told you about the two things every human is really, really good at?
Anyway, Compiler Explorer has this annoying little feature. Is has two sections with the identical header "CLANG X86-64" and I happened to pick the one that only has official releases.
Initially, I picked clang 21. Well, that same code I posted in the link above has different behavior if you change the clang version to anything prior to trunk.
Look here <https://godbolt.org/z/9cshPonz9> for something a bit different.
Clang 21 has a very different implementation of `__builtin_is_implicit_lifetime` than trunk, especially regarding what qualifies as a "trivial eligible constructor."
This result only magnifies your earlier claims about wording, and sent chasing some rabbits.
I'll get to all of this, but this is not a short email.
If I had a blog, I'd just write a cool (or not so cool) article, and post the link.
Instead, you get some more markdown.
---
## The Roller Coaster Begins
When I saw what clang 21 was yielding (and hadn't yet run it on trunk), I decided to perform a more simple test:
```cpp
class Bar {
int i;
public:
Bar(int i = 42) : i(i) { }
Bar(Bar const &) = delete;
};
static_assert(not std::is_implicit_lifetime_v<Bar>);
```
Here is the code in Compiler Explorer <https://godbolt.org/z/5Y3Ef11s4>.
It is worth noting that while `std::is_implicit_lifetime` was approved with C++23, it only recently got implementations in clang and gcc.
Clang 20, released in March 2025, and gcc 16 (yet to be released) are the first versions to have implementations.
Both of these implementations are very new.
This example is structurally identical to `std::atomic<int>`: user-provided default constructor, deleted copy/move.
By my reading of the standard, this should NOT be an implicit-lifetime type.
For those who, for some reason, can't click on the link, it shows:
- libstdc++ (GCC) agrees with me. The assertion passes.
- libc++ (Clang 20/21) does not. The assertion *fails*. It thinks `Bar` IS an implicit-lifetime type.
So, I then tested `std::atomic<int>` directly <https://godbolt.org/z/hsGM6TnnP>.
Same divergence.
My first reaction was... On second thought, I shouldn't repeat my first reaction here.
My second reaction was: *Oh no. I've wasted everyone's time. The libc++ implementers clearly have a different — and possibly better — interpretation of "trivial eligible constructor."*
---
## The Jubilation Phase
See, Arthur, you were right.
You said maybe the real issue was option (D) — that the definition of implicit-lifetime itself might need adjustment, not that we need new constructor syntax.
And here was libc++ apparently agreeing!
Their interpretation seemed to be: "If the object representation could theoretically be trivially constructed — if it's just bytes that don't need magic initialization — then it's implicit-lifetime, regardless of what constructors are actually declared."
This is *philosophically better*.
It matches physical reality.
It's what implicit-lifetime *should* mean.
I was ready to pivot the entire paper.
Maybe even just file an editorial issue to clarify the wording.
The libc++ folks had done my job for me!
I started composing a humble email admitting I'd been tilting at windmills.
---
## The Investigation
But something nagged at me.
I've been beating this implicit-lifetime type drum for two years.
People not on the committee had lots of questions.
People on the committee had the same response: write a paper.
Anyhow, I wanted to see how it was implemented.
So, I did an update of my local fork and took a look at the implementation, which is in `SemaTypeTraits.cpp.`
The implementation I found iterates through actual constructors and checks if each is both trivial and eligible (non-deleted):
```cpp
for (CXXConstructorDecl *Ctr : RD->ctors()) {
if (Ctr->isIneligibleOrNotSelected() || Ctr->isDeleted())
continue;
if (Ctr->isTrivial())
return true;
}
```
I'm far from a clang implementation expert, but that code didn't seem like it would produce the behavior I was seeing on Compiler Explorer.
This code should return `false` for `Bar`.
Then I went looking for tests, and found a bunch of tests that looked very much like my examples.
So I checked the git history.
---
## The Plot Twist
```
commit e5bbc9feae3c
Author: Corentin Jabot <corentinjabot_at_[hidden]>
Date: Mon Sep 29 15:55:35 2025 +0200
[Clang] Fixes __builtin_is_implicit_lifetime for types with deleted ctrs (#161163)
We failed to check that the trivial constructor where eligible (this
implies non deleted).
Fixes #160610
```
*Oh.*
The OLD code (in Clang 20 and 21) was:
```cpp
if (RD->hasTrivialDestructor() && (!Dtor || !Dtor->isDeleted()))
if (RD->hasTrivialDefaultConstructor() ||
RD->hasTrivialCopyConstructor() || RD->hasTrivialMoveConstructor())
return true;
```
See the bug?
`hasTrivialCopyConstructor()` returns `true` if the copy constructor *would be* trivial — even if it's deleted.
The old code never checked whether the constructor was actually usable.
For `Bar`, `hasTrivialCopyConstructor()` returns `true` because copying an `int` is trivial.
The fact that the copy constructor is *deleted* didn't matter to the old code.
That's a bug.
And it was fixed on September 29, 2025.
Now is where I could go down another side tunnel on the definition of a bug, because the old buggy behavior is exactly what I want to happen.
---
## The Timeline
| Event | Date |
|-------|------|
| Clang 21.1.0 released | August 26, 2025 |
| **Bug fix committed** | **September 29, 2025** |
| Clang 21.1.5 (latest 21.x) | Still doesn't have fix |
| Fix ships | Clang 22 |
I confirmed this on Compiler Explorer by comparing Clang 21 vs. Clang trunk:
```cpp
static_assert(not std::is_implicit_lifetime_v<Bar>);
static_assert(not std::is_implicit_lifetime_v<std::atomic<int>>);
```
Both pass on trunk, and fail on clang 21.
---
## The Dejection Phase
So.
The libc++ developers don't have a more permissive interpretation.
They had a bug.
They've already fixed it.
When Clang 22 ships, both gcc and clang will agree: `std::atomic<int>` is NOT an implicit-lifetime type.
Which means my original problem is real.
Unfortunately, my paper isn't tilting at windmills.
I'm honestly not sure how I should feel about this.
---
## The Silver Lining
But the "bug" proves my desired behavior is implementable.
Clang 20 and 21 shipped with an implementation that effectively asked: "Could this type's representation be trivially constructed, ignoring what constructors are actually declared?"
And it worked.
No compiler explosions.
No subtle miscompilations.
It just, did the more permissive, more practical thing.
The only reason it was "wrong" is that it didn't match an interpretation of the standard's wording.
But the standard's wording is exactly what I'm arguing should change (or be worked around).
To be fair, the whole implicit lifetime thing is mostly a compiler thing, and since it is relatively new and a niche topic, there is not much code exercising the type trait itself.
So if the committee prefers option (D) — relaxing the definition of implicit-lifetime — we now have existence proof that it's implementable.
Clang did it for two major releases.
If the committee prefers option (C) — my proposal for user-defined trivial constructors — that's also implementable, and keeps the existing definition intact.
Either way, I think something needs to be done.
## Chapter and Verse (As Requested)
And, for completeness, here are the normative references:
**[basic.types.general]/9**: "Scalar types, implicit-lifetime class types ([class.prop]), array types, and cv-qualified versions of these types are collectively called implicit-lifetime types."
**[class.prop]/16**: "A class S is an implicit-lifetime class if (16.1) it is an aggregate whose destructor is not user-provided or (16.2) it has at least one trivial eligible constructor and a trivial, non-deleted destructor."
**[special]/6** defines "eligible special member function" — not deleted, constraints satisfied, not more-constrained than another candidate. Notably, no access control requirement.
**[atomics.lockfree]/2**: "This restriction enables communication by memory that is mapped into a process more than once and by memory that is shared between two processes." — The standard explicitly contemplates cross-process atomics.
**P2674** introduced `std::is_implicit_lifetime` and noted it requires compiler magic (can't be implemented purely in library) due to CWG2605.
**Implementation experience:**
FWIW, here are two bugs (one with clang, the other with gcc) I'm now somewhat intimately familiar with.
Neither have yet shipped.
I suspect these will not be the last changes made to this type trait in either compiler, even it the wording in the standard does not change.
- [Clang bug #160610](https://github.com/llvm/llvm-project/issues/160610): Incorrectly returned `true` for types with deleted trivial constructors (fixed September 2025, ships in Clang 22)
- [GCC bug #122690](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=122690): Incorrectly returned `false` when the trivial constructor was private/protected (merged, ships with gcc 16)
Both bugs demonstrate that "trivial eligible constructor" is trickier to implement than it appears — and perhaps trickier to *define* than the current wording suggests.
---
## Where This Leaves Us
The implementations will converge on the strict interpretation:
- A class needs an actual, usable, trivial constructor to be implicit-lifetime
- `std::atomic<int>` has no such constructor (default is user-provided, copy/move are deleted)
- Therefore `std::atomic<int>` is not an implicit-lifetime type
- Therefore using it in shared memory via `start_lifetime_as` is undefined behavior
Your suggestion about option (D) — changing the definition of implicit-lifetime — is worth considering.
It would be simpler.
And Clang's "bug" proves it's implementable without breaking anything.
But committees tend to be conservative about changing definitions that were carefully designed and are already standardized.
My proposal (option C) works within the existing framework: if we allow user-defined trivial constructors, types can opt into implicit-lifetime status without changing what implicit-lifetime means.
Unless you think differently, I'll update the paper with both options and let EWG decide which path they prefer.
---
Thanks for pushing back on the details.
I went down a rabbit hole, experienced the full emotional spectrum, discovered a compiler bug, confirmed it was fixed, verified the fix on trunk, and came out the other side with actual evidence instead of hand-waving.
That's probably how this kind of stuff is supposed to go, right?
Jody
---
**P.S.** — The test case Clang added to verify their fix is almost identical to my `Bar` but it is even more surprising that you can call `std::start_lifetime_as` with it:
```cpp
class UserProvidedConstructor {
public:
UserProvidedConstructor() {}
UserProvidedConstructor(const UserProvidedConstructor&) = delete;
};
static_assert(!__builtin_is_implicit_lifetime(UserProvidedConstructor));
```
At least my instincts about what the standard *means* were likely correct.
Small consolation, but I'll take it.
Received on 2026-01-11 15:07:35
