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@lists.isocpp.org> 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@lists.isocpp.org>
Gesendet: Mi 10.12.2025 22:20
Betreff: Re: [std-proposals] [PXXXXR0] Add a New Keyword `undecl`
An: std-proposals@lists.isocpp.org;
CC: Jarrad Waterloo <descender76@gmail.com>;
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@lists.isocpp.org> wrote:


> On Dec 10, 2025, at 9:46 AM, David Brown via Std-Proposals <std-proposals@lists.isocpp.org> 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@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
-- 
 Std-Proposals mailing list
 Std-Proposals@lists.isocpp.org
 https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
 

 

--
Std-Proposals mailing list
Std-Proposals@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals