Date: Wed, 10 Dec 2025 20:51:52 -0500
Sebastian,
*About extending the lifetime with span & co.*
*std::span is non-owning, in your idea the lifetime would be extended as
long as the std::span view lives*
YES
>From the standard
6.8.7 Temporary objects [class.temporary] Paragraph 6 (6.8) [3:6]
*[Example 3 :*
const int& x = (const int&)1; // temporary for value 1 has same lifetime as x
`1` is the object
`x` if the reference type in this case a reference
So I am saying do the same thing when creating a variable to any pure
reference type being initialized with its referred to object.
*What happens, if you take a subspan and drop the span?*
*Will the subspan inherit the relationship to the original std::vector?*
*Where is the bookkeeping without the type knowing?*
Please give actual examples so I can better respond. What I say next may be
a little off.
const int& x = (const int&)1; // temporary for value 1 has same lifetime as
x
const int& y = x;
So no lifetime extension occurs when initializing a reference with a
reference. The reference is just copied. There is still NO immediate
dangling. This equally applies to getting references to members which is
similar in concept to subspan.
We are not fixing all dangling, just these immediate dangling, as it is
fixed elsewhere in C++.
*I think in practice this would not work.*
How does it happen and work with references even NOW?
Look at the specific example I am talking about.
span a = vector{...};
This expression always dangle and immediately dangle NOW.
If that same expression was rewritten.
vector some_name_we_will_only_use_once{};
span a = some_name_we_will_only_use_once;
`a` would NOT immediately dangle.
This works the same for string_view and function_ref.
It could also work for reference_wrapper and optional<&> where temporaries
are currently not allowed if the lifetime extended objects where treated as
anonymously named objects, l values, as in the case of the expanded code
and the new _.
Besides _ there is even more precedent for this. Consider the following two:
*initializer_list*
std::initializer_list<int> il{-3, -2, -1};
initializer_list is a pure alias type and its backing array is the object
that is treated just like temporaries.
*9.5.5 List-initialization [dcl.init.list] Paragraph 6 [3:7]The backing
array of an initializer_list has a lifetime of static storage duration
unless one member of the array is initialized with a non const variable in
which case the lifetime is the same as any other temporary object (6.8.7),
except that initializing an initializer_list object from the array extends
the lifetime of the array exactly like binding a reference to a temporary."*
initializer_list is not far off than span and string_view!
*structured bindings*
structured bindings is another precedent
[I haven't looked for the standard wording yet.]
https://en.cppreference.com/w/cpp/language/structured_binding.html
*Binds the specified names to subobjects or elements of the
initializer.Like a reference, a structured binding is an alias to an
existing object. Unlike a reference, a structured binding does not have to
be of a reference type.*
auto [a, b, c] = C();
a, b and c are the aliases and C() is the object, yet no immediate dangling
So what part of this seems impractical to work?
Matter of fact, I have simulated this via structured bindings.
template<class Base, class Derived>
requires (!std::is_void_v<Base>)
&& (!std::is_void_v<Derived>)
&& (!std::is_pointer_v<Base>)
&& (!std::is_pointer_v<Derived>)
&& (!std::is_reference_v<Base>)
&& (!std::is_reference_v<Derived>)
&& (!std::derived_from<Derived, Base>)
struct inplace_base_view
{
public:
template <class... Args>
inplace_base_view(Args&&... args) :
derived{std::forward<decltype(args)>(args)...}/*, base{derived}*/ {}
constexpr inplace_base_view(const inplace_base_view&) noexcept = delete;
constexpr inplace_base_view(inplace_base_view&&) noexcept = delete;
constexpr inplace_base_view& operator=(const inplace_base_view&)
noexcept = delete;
constexpr inplace_base_view& operator=(inplace_base_view&&) noexcept =
delete;
template <std::size_t I>
Base get() {
return Base{derived};
}
template <std::size_t I>
const Base get() const {
return Base{derived};
}
private:
Derived derived;
};
namespace std {
template<class Base, class Derived>
struct tuple_size<inplace_base_view<Base, Derived>> {
static constexpr std::size_t value = 1;
};
template<class Base, class Derived>
struct tuple_element<0, inplace_base_view<Base, Derived>> {
using type = Base;
};
}
...
struct DerivedTest
{
DerivedTest()
{
std::cout << "DerivedTest constructor called\n";
}
~DerivedTest()
{
std::cout << "DerivedTest destructor called\n";
}
};
struct BaseTest
{
BaseTest()
{
std::cout << "BaseTest constructor called\n";
}
BaseTest(const DerivedTest&)
{
std::cout << "BaseTest constructor called\n";
}
~BaseTest()
{
std::cout << "BaseTest destructor called\n";
}
};
// like span this is a non invalidating view of a vector
class leastp final
{
public:
constexpr leastp(std::vector<int>& v) : v{v} {}
constexpr leastp(const leastp&) noexcept = default;
constexpr leastp(leastp&&) noexcept = default;
constexpr leastp& operator=(const leastp& other) noexcept
{
if (this != &other)
{
// lifetime of *this ends
this->~leastp();
// new object of type leastp created
new (this) leastp(other);
}
return *this;
}
constexpr leastp& operator=(leastp&& other) noexcept
{
if (this != &other)
{
// lifetime of *this ends
this->~leastp();
// new object of type leastp created
new (this) leastp(other);
}
return *this;
}
// https://csrc.nist.gov/glossary/term/least_privilege
// least privilege: A security principle that a system
// should restrict the access privileges of users (or
// processes acting on behalf of users) to the minimum
// necessary to accomplish assigned tasks.
constexpr size_t size() const
{
return v.size();
}
private:
std::vector<int>& v;
};
...
auto [niv1] = inplace_base_view<std::span<int>, std::vector<int>>{ 42 };
auto [niv2] = inplace_base_view<const std::span<int>,
std::vector<int>>{ 42 };
auto [niv3] = inplace_base_view<leastp, std::vector<int>>{ 42 };
auto [niv4] = inplace_base_view<const leastp, std::vector<int>>{ 42 };
auto [btdt] = inplace_base_view<BaseTest, DerivedTest>{};
auto vs = niv1.size();
vs = niv2.size();
vs = niv3.size();
vs = niv4.size();
In some ways this is similar to inheritance, just that the two types are
not derived one from another.
In inheritance, C++ can do the following:
Base* b = new Derived{};
Well that is great for dynamically allocated memory but what about stack
allocated memory.
Base& b = Derived{};// ill formed ... error: cannot bind non-const lvalue
reference of type 'base&' to an rvalue of type 'base'x86-64 gcc 15.2 #1
And again what about when the two types are not related as in the case of
span and vector.
On Wed, Dec 10, 2025 at 5:42 PM Sebastian Wittmeier via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> About extending the lifetime with span & co.
>
> std::span is non-owning, in your idea the lifetime would be extended as
> long as the std::span view lives
>
> What happens, if you take a subspan and drop the span?
>
> Will the subspan inherit the relationship to the original std::vector?
>
> Where is the bookkeeping without the type knowing?
>
>
>
> I think in practice this would not work.
>
>
>
>
>
>
> -----Ursprüngliche Nachricht-----
> *Von:* Jarrad Waterloo via Std-Proposals <std-proposals_at_[hidden]>
> *Gesendet:* Mi 10.12.2025 22:20
> *Betreff:* Re: [std-proposals] [PXXXXR0] Add a New Keyword `undecl`
> *An:* std-proposals_at_[hidden];
> *CC:* Jarrad Waterloo <descender76_at_[hidden]>;
> I'd like to summarize a few points in case they have been missed.
>
> `undecl`, to some, seems like it is solving half of a problem
> It might be good if there were the following two versions of it instead:
>
> undecl(destroy) // as the undecl author envisioned it
> undecl(hide) // as it is related to shadowing but not shadowing
>
> Likely neither `destroy` nor `hide` should be the default. So don't waste
> 10 years debating the default when you can have 10 years of explicit usage
> telling us what the default will be.
>
> The following does not involve any shadowing though it is related to
> shadowing and undecl(hide).
>
> string_view a = string{};// immediate dangling
> //or
> span a = vector{};// immediate dangling
>
> The defensive programming workaround is as follows.
>
> struct dename{}; // simply any 0 size tag type without any functionality
> will do
> ...
> ...
> ...
> vector some_name_we_will_only_use_once{};
> span a = some_name_we_will_only_use_once;
> {
> dename some_name_we_will_only_use_once;
> // MOVE all other code into this block
> }
>
> OR with undecl(hide)
>
> vector some_name_we_will_only_use_once{};
> span a = some_name_we_will_only_use_once;
> undecl(hide) some_name_we_will_only_use_once;
>
> NOTE: The current status quo of `span a = vector{};` is that programmers
> either immediately dangle OR have to write verbose code OR worse lazily
> have superfluous aliases out there contributing to invalidation,
> concurrency errors and/or harder to understand code.
>
> Making `span a = vector{};` NOT immediate dangling BY allowing lifetime
> extension to apply to all pure alias types instead of just references
> DOESN'T break any existing code which isn't already immediately dangling
> and it doesn't require any new keywords or annotations. Rather it has
> numerous benefits.
>
> This would remove an embarrassing form of dangling associated with the
> creation of temporaries.
> This would remove an inconsistency that lifetime extension works on
> reference but not reference like types.
> This allows the programmer to better manage invalidation without having to
> create superfluous aliases that only further enable invalidation.
>
>
> On Wed, Dec 10, 2025 at 3:14 PM Simon Schröder via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>
>
> > On Dec 10, 2025, at 9:46 AM, David Brown via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
> >
> > 1. Copy-and-paste code (redeclaration without undecl) :
> This variant I don’t really like. It would be too easy to accidentally
> shadow a variable. If I have working code and I accidentally redeclare a
> variable (possibly with the same type) right between the declaration of the
> old variable and the previously last use of the variable, I will break that
> code without a compiler error. In my opinion it is a good thing to have
> compiler errors when you try to do something stupid. (Maybe we could allow
> shadowing without undecl and then unshadow using undecl. Anyway, it would
> be much better if you’d had to be explicit about shadowing, e.g. with an
> attribute [[shadow]]. Nothing accidental about that.) BTW, if you want a
> variable you can shadow, use _.
> >
> > 2. Explicitly ending the lifetime of a variable and making it unusable:
> >
> Other languages (e.g. Rust) use the ‘keyword’ ‘drop’ for this. I’m not
> sure if ‘drop’ will collide with common variable names (or more likely with
> function names). ‘undecl’ certainly looks like it is not yet used in any
> C++ code. As has been replied already: std::optional already allows to end
> the lifetime of a variable early (but without the added “benefit” of being
> able to reuse its name (if it’s not the same type)).
> >
> > 3. Const-locking identifiers (redeclaration without undecl) :
> >
> This is a nice idea I could get behind.
> >
> > auto x = get_x();
> > change_x(&x);
> > const auto x = x;
> > change_x(&x); // Compile-time error
> >
> Currently syntax like the line
> const auto x = x;
> would first declare a variable ‘x’ and then assign it to itself. We should
> have some other syntax for this feature. It is currently already possible
> to write this kind of buggy code if ‘x’ comes from an outer scope. There is
> no reason for this to behave differently if we shadow an ‘x’ from the same
> scope. And certainly someone is “relying” on this bug and we’d break
> someone’s code if we fix this.
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
*About extending the lifetime with span & co.*
*std::span is non-owning, in your idea the lifetime would be extended as
long as the std::span view lives*
YES
>From the standard
6.8.7 Temporary objects [class.temporary] Paragraph 6 (6.8) [3:6]
*[Example 3 :*
const int& x = (const int&)1; // temporary for value 1 has same lifetime as x
`1` is the object
`x` if the reference type in this case a reference
So I am saying do the same thing when creating a variable to any pure
reference type being initialized with its referred to object.
*What happens, if you take a subspan and drop the span?*
*Will the subspan inherit the relationship to the original std::vector?*
*Where is the bookkeeping without the type knowing?*
Please give actual examples so I can better respond. What I say next may be
a little off.
const int& x = (const int&)1; // temporary for value 1 has same lifetime as
x
const int& y = x;
So no lifetime extension occurs when initializing a reference with a
reference. The reference is just copied. There is still NO immediate
dangling. This equally applies to getting references to members which is
similar in concept to subspan.
We are not fixing all dangling, just these immediate dangling, as it is
fixed elsewhere in C++.
*I think in practice this would not work.*
How does it happen and work with references even NOW?
Look at the specific example I am talking about.
span a = vector{...};
This expression always dangle and immediately dangle NOW.
If that same expression was rewritten.
vector some_name_we_will_only_use_once{};
span a = some_name_we_will_only_use_once;
`a` would NOT immediately dangle.
This works the same for string_view and function_ref.
It could also work for reference_wrapper and optional<&> where temporaries
are currently not allowed if the lifetime extended objects where treated as
anonymously named objects, l values, as in the case of the expanded code
and the new _.
Besides _ there is even more precedent for this. Consider the following two:
*initializer_list*
std::initializer_list<int> il{-3, -2, -1};
initializer_list is a pure alias type and its backing array is the object
that is treated just like temporaries.
*9.5.5 List-initialization [dcl.init.list] Paragraph 6 [3:7]The backing
array of an initializer_list has a lifetime of static storage duration
unless one member of the array is initialized with a non const variable in
which case the lifetime is the same as any other temporary object (6.8.7),
except that initializing an initializer_list object from the array extends
the lifetime of the array exactly like binding a reference to a temporary."*
initializer_list is not far off than span and string_view!
*structured bindings*
structured bindings is another precedent
[I haven't looked for the standard wording yet.]
https://en.cppreference.com/w/cpp/language/structured_binding.html
*Binds the specified names to subobjects or elements of the
initializer.Like a reference, a structured binding is an alias to an
existing object. Unlike a reference, a structured binding does not have to
be of a reference type.*
auto [a, b, c] = C();
a, b and c are the aliases and C() is the object, yet no immediate dangling
So what part of this seems impractical to work?
Matter of fact, I have simulated this via structured bindings.
template<class Base, class Derived>
requires (!std::is_void_v<Base>)
&& (!std::is_void_v<Derived>)
&& (!std::is_pointer_v<Base>)
&& (!std::is_pointer_v<Derived>)
&& (!std::is_reference_v<Base>)
&& (!std::is_reference_v<Derived>)
&& (!std::derived_from<Derived, Base>)
struct inplace_base_view
{
public:
template <class... Args>
inplace_base_view(Args&&... args) :
derived{std::forward<decltype(args)>(args)...}/*, base{derived}*/ {}
constexpr inplace_base_view(const inplace_base_view&) noexcept = delete;
constexpr inplace_base_view(inplace_base_view&&) noexcept = delete;
constexpr inplace_base_view& operator=(const inplace_base_view&)
noexcept = delete;
constexpr inplace_base_view& operator=(inplace_base_view&&) noexcept =
delete;
template <std::size_t I>
Base get() {
return Base{derived};
}
template <std::size_t I>
const Base get() const {
return Base{derived};
}
private:
Derived derived;
};
namespace std {
template<class Base, class Derived>
struct tuple_size<inplace_base_view<Base, Derived>> {
static constexpr std::size_t value = 1;
};
template<class Base, class Derived>
struct tuple_element<0, inplace_base_view<Base, Derived>> {
using type = Base;
};
}
...
struct DerivedTest
{
DerivedTest()
{
std::cout << "DerivedTest constructor called\n";
}
~DerivedTest()
{
std::cout << "DerivedTest destructor called\n";
}
};
struct BaseTest
{
BaseTest()
{
std::cout << "BaseTest constructor called\n";
}
BaseTest(const DerivedTest&)
{
std::cout << "BaseTest constructor called\n";
}
~BaseTest()
{
std::cout << "BaseTest destructor called\n";
}
};
// like span this is a non invalidating view of a vector
class leastp final
{
public:
constexpr leastp(std::vector<int>& v) : v{v} {}
constexpr leastp(const leastp&) noexcept = default;
constexpr leastp(leastp&&) noexcept = default;
constexpr leastp& operator=(const leastp& other) noexcept
{
if (this != &other)
{
// lifetime of *this ends
this->~leastp();
// new object of type leastp created
new (this) leastp(other);
}
return *this;
}
constexpr leastp& operator=(leastp&& other) noexcept
{
if (this != &other)
{
// lifetime of *this ends
this->~leastp();
// new object of type leastp created
new (this) leastp(other);
}
return *this;
}
// https://csrc.nist.gov/glossary/term/least_privilege
// least privilege: A security principle that a system
// should restrict the access privileges of users (or
// processes acting on behalf of users) to the minimum
// necessary to accomplish assigned tasks.
constexpr size_t size() const
{
return v.size();
}
private:
std::vector<int>& v;
};
...
auto [niv1] = inplace_base_view<std::span<int>, std::vector<int>>{ 42 };
auto [niv2] = inplace_base_view<const std::span<int>,
std::vector<int>>{ 42 };
auto [niv3] = inplace_base_view<leastp, std::vector<int>>{ 42 };
auto [niv4] = inplace_base_view<const leastp, std::vector<int>>{ 42 };
auto [btdt] = inplace_base_view<BaseTest, DerivedTest>{};
auto vs = niv1.size();
vs = niv2.size();
vs = niv3.size();
vs = niv4.size();
In some ways this is similar to inheritance, just that the two types are
not derived one from another.
In inheritance, C++ can do the following:
Base* b = new Derived{};
Well that is great for dynamically allocated memory but what about stack
allocated memory.
Base& b = Derived{};// ill formed ... error: cannot bind non-const lvalue
reference of type 'base&' to an rvalue of type 'base'x86-64 gcc 15.2 #1
And again what about when the two types are not related as in the case of
span and vector.
On Wed, Dec 10, 2025 at 5:42 PM Sebastian Wittmeier via Std-Proposals <
std-proposals_at_[hidden]> wrote:
> About extending the lifetime with span & co.
>
> std::span is non-owning, in your idea the lifetime would be extended as
> long as the std::span view lives
>
> What happens, if you take a subspan and drop the span?
>
> Will the subspan inherit the relationship to the original std::vector?
>
> Where is the bookkeeping without the type knowing?
>
>
>
> I think in practice this would not work.
>
>
>
>
>
>
> -----Ursprüngliche Nachricht-----
> *Von:* Jarrad Waterloo via Std-Proposals <std-proposals_at_[hidden]>
> *Gesendet:* Mi 10.12.2025 22:20
> *Betreff:* Re: [std-proposals] [PXXXXR0] Add a New Keyword `undecl`
> *An:* std-proposals_at_[hidden];
> *CC:* Jarrad Waterloo <descender76_at_[hidden]>;
> I'd like to summarize a few points in case they have been missed.
>
> `undecl`, to some, seems like it is solving half of a problem
> It might be good if there were the following two versions of it instead:
>
> undecl(destroy) // as the undecl author envisioned it
> undecl(hide) // as it is related to shadowing but not shadowing
>
> Likely neither `destroy` nor `hide` should be the default. So don't waste
> 10 years debating the default when you can have 10 years of explicit usage
> telling us what the default will be.
>
> The following does not involve any shadowing though it is related to
> shadowing and undecl(hide).
>
> string_view a = string{};// immediate dangling
> //or
> span a = vector{};// immediate dangling
>
> The defensive programming workaround is as follows.
>
> struct dename{}; // simply any 0 size tag type without any functionality
> will do
> ...
> ...
> ...
> vector some_name_we_will_only_use_once{};
> span a = some_name_we_will_only_use_once;
> {
> dename some_name_we_will_only_use_once;
> // MOVE all other code into this block
> }
>
> OR with undecl(hide)
>
> vector some_name_we_will_only_use_once{};
> span a = some_name_we_will_only_use_once;
> undecl(hide) some_name_we_will_only_use_once;
>
> NOTE: The current status quo of `span a = vector{};` is that programmers
> either immediately dangle OR have to write verbose code OR worse lazily
> have superfluous aliases out there contributing to invalidation,
> concurrency errors and/or harder to understand code.
>
> Making `span a = vector{};` NOT immediate dangling BY allowing lifetime
> extension to apply to all pure alias types instead of just references
> DOESN'T break any existing code which isn't already immediately dangling
> and it doesn't require any new keywords or annotations. Rather it has
> numerous benefits.
>
> This would remove an embarrassing form of dangling associated with the
> creation of temporaries.
> This would remove an inconsistency that lifetime extension works on
> reference but not reference like types.
> This allows the programmer to better manage invalidation without having to
> create superfluous aliases that only further enable invalidation.
>
>
> On Wed, Dec 10, 2025 at 3:14 PM Simon Schröder via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>
>
> > On Dec 10, 2025, at 9:46 AM, David Brown via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
> >
> > 1. Copy-and-paste code (redeclaration without undecl) :
> This variant I don’t really like. It would be too easy to accidentally
> shadow a variable. If I have working code and I accidentally redeclare a
> variable (possibly with the same type) right between the declaration of the
> old variable and the previously last use of the variable, I will break that
> code without a compiler error. In my opinion it is a good thing to have
> compiler errors when you try to do something stupid. (Maybe we could allow
> shadowing without undecl and then unshadow using undecl. Anyway, it would
> be much better if you’d had to be explicit about shadowing, e.g. with an
> attribute [[shadow]]. Nothing accidental about that.) BTW, if you want a
> variable you can shadow, use _.
> >
> > 2. Explicitly ending the lifetime of a variable and making it unusable:
> >
> Other languages (e.g. Rust) use the ‘keyword’ ‘drop’ for this. I’m not
> sure if ‘drop’ will collide with common variable names (or more likely with
> function names). ‘undecl’ certainly looks like it is not yet used in any
> C++ code. As has been replied already: std::optional already allows to end
> the lifetime of a variable early (but without the added “benefit” of being
> able to reuse its name (if it’s not the same type)).
> >
> > 3. Const-locking identifiers (redeclaration without undecl) :
> >
> This is a nice idea I could get behind.
> >
> > auto x = get_x();
> > change_x(&x);
> > const auto x = x;
> > change_x(&x); // Compile-time error
> >
> Currently syntax like the line
> const auto x = x;
> would first declare a variable ‘x’ and then assign it to itself. We should
> have some other syntax for this feature. It is currently already possible
> to write this kind of buggy code if ‘x’ comes from an outer scope. There is
> no reason for this to behave differently if we shadow an ‘x’ from the same
> scope. And certainly someone is “relying” on this bug and we’d break
> someone’s code if we fix this.
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
Received on 2025-12-11 01:52:07
