C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Extension to runtime polymorphism proposed

From: Simon Schröder <dr.simon.schroeder_at_[hidden]>
Date: Sat, 11 Apr 2026 08:05:08 +0200


On Apr 11, 2026, at 5:00 AM, Muneem via Std-Proposals <std-proposals_at_[hidden]> wrote:


Mr.thaigo did mistake my heterogeneous list with a variant, but thanks for reminding him for me. We all rely on community work to correct each other.

My code was just an example of the limitations not the end proposal. Adding references to variants would dilute the idea of variants, like variants are supposed to be closed set duck typing mechanisms or as one would say it is a union with tags.
First and foremost I see variants as a way to a) make unions safe and b) allow to store variables on the stack/in place instead of the heap. Before you would only create a std::vector<object*> which would mean that the actual objects would be allocated on the heap. Variants is a way to get rid of too many dynamic allocations and allows better RAII (more objects on the stack means fewer bugs).
By adding references you are restricting the union/variant into a strict dynamic type that views an object.
I’m not diluting any idea, I’m extending it. There is precedence that there is a proposal to extend std::optional to allow for references. It is a complicated topic, but it is how C++ standardization usually works: we take something that already exists and extend it if the extension is meaningful and valuable. Sometimes the writers of the initial proposal didn’t think of everything, and sometimes they purposely decided to not include a feature in the initial proposal to get it through standardization faster. And adding references to std::variant does not restrict anything: the user of std: variant is free to choose the existing behavior or references (or even mix both). Existing code would still work exactly the same as before. The *user* of the std::variant can restrict the variant to references; the standard on the other hand would extend it.
The same goes to tuples, tuples are for compile time typed lists so adding runtime indexing will dilute the idea and probably make many existing documentations false.
I think it is rather frustrating that we can’t use runtime indices with std::tuple. I bet that many have already programmed around it. Or they have chosen std::vector<std::variant> instead (which is definitely not the same). I think it is worthwhile to extend std::tuple like this (if it is easy to implement, which it might be now). static const member variables are also compile time objects. So, shouldn’t we use them at runtime because it dilutes the idea? Aren’t we allowed to use the output from reflection at runtime because reflection works at compile time? There is definitely a need to bridge compile time and runtime in many places. I claim there is a need for indexing tuple as well.
For example the new defintion of what is a standard layout would suddenly now omit certain specializations variants from the list. Requiring conversions from a variant of multiple types into an optional<T&> of a single type will require underlying language support which again is what I am proposing.
No, no language support required for this. Even restricting std::optional and std::variant to not allow references is not restricted by the language. Apart from the references I could easily write a conversion from std::variant to std::optional: use std::hold_alternative to check if the requested type of the optional is the current type of the variant; then either return this using std::get or return null nullopt. Done.
The reason I don't want the underlying implementation support to be wrapped in standard libraries is to not dilute the existing concepts that already exist, and to let the support provided be freely expressed.
It is not dilution of existing concepts, but extension of existing concepts. And the best part is that it is not only an advantage for your specific problem, but brings an advantage for everybody using these types. There will be a lot more use cases. And the changes to the standard are minor and can be done in separate proposals. This makes them more likely to be accepted. And people will understand these small changes on existing types easily instead of your proposal that currently nobody really understands. This alone makes it a better strategy to use existing types. (Large, complicated proposals tend to get pushed back because the committee cannot review _all_ proposals for the next standard.)
In your case, you can't copy/move because it's wrapped in the standard library. In my case, it can be copied and moved, since it's not wrapped in anything.
You are wrong: std::optional and std::variant allow copy and move. This is a point why I have suggested them: the user can pick himself what to do. In your current example implementation Element_t always points inside the list and it is not possible to get a copy of the underlying value. Optional and variant already have this built in.
The issue is that at some point you will need support from the implementation(in your example, conversion from variants<T&...> to optional<T&> but would you want that support to be suppressed within the bounds of the interface of the facilities that you want to extend with that support, or do you want it to be freely expressed using the language. A good analogy would be:If you are getting a subway sandwich, get a healthy one with vinegar and multigrain instead of the "sodium packed" standard sandwich. The standard one is probably considered more socially acceptable since everyone eats it but it's also more constraint in that you can't eat too much of it. Same goes over here that the language one will be free to have its own new interface, ABI, and everything, but if the same thing is provided by extending the standard library then even though it is "standard", you can consume the support the implementation provides completely.
You are still claiming that it is possible to invent types that are more efficient than std::tuple and std::variant (and maybe std::optional which I brought into play). It is not possible to make something more efficient than std::tuple which only stores its types and doesn’t need any extra bookkeeping because everything is known at compile time. And variant uses the least space possible for storage with a tag. This also cannot be further reduced. And optional also just has storage and a tag. All that is necessary if you want to be able to use these types at runtime. And—at least theoretically—for compile time known indices/tags the compiler could be able to optimize out everything.

On Fri, 10 Apr 2026, 11:01 pm Simon Schröder via Std-Proposals, <std-proposals_at_[hidden]> wrote:
On 10. Apr 2026, at 17:39, Thiago Macieira via Std-Proposals <std-proposals_at_[hidden]> wrote:
>
> On Thursday, 9 April 2026 23:30:10 Pacific Daylight Time Muneem via Std-
> Proposals wrote:
>> template <std::size_t N, typename type_set, typename... Tail>
>> class heterogeneous_list_impl;
>>
>> template <std::size_t N, typename Head_t, typename... Tail>
>> class heterogeneous_list<N, Head_t, Tail...> : public
>> heterogeneous_list_impl<N - 1,Element_t<N, Head_t, Tail...>,Tail...> {
>>    Head_t Element;
>> public:
>>    using type_set = Element_t<N, Head_t, Tail...>;
>>
>>    type_set operator[](std::size_t index) {
>>        if (index == N) {
>>            return type_set(Element);
>>        } else if constexpr (N > 0) {
>>            return this->heterogeneous_list_impl<N - 1, type_set,
>> Tail...>::operator[](index);
>>        } else {
>>            throw std::string{"out of bound access"};
>>        }
>>    }
>> };
>
> You do realise this is just std::variant with one extra operator, right?
>
> That operator[] could easily be implemented in the current std::variant, with
> a return either of a new type such as variant_element_ref or simply 
> std::variant<std::add_reference_t<Types>...>. That return type is your
> innovation, not the heterogeneous list type (which is not a list).
>
Thiago, are you now mixing up std::variant and std::tuple? To me, it looks like heterogeneous_list is a std::tuple and Element_t is a std::variant. However, Element_t has a pointer to the proper element inside the heterogeneous_list in Muneems example implementation; so it would be comparable to a std::variant that stores a reference. (Which is partially what you said.)

I am a little bit confused by the example implementation: I fail to see how to actually store values inside the heterogeneous_list. Either there is only memory for the first element in the list or there is just a single list for each combination of <N,Head_t,Tail…>. I’m not really figuring this out by just looking at the code.

Basically, this is what I’ve been saying all along: the heterogeneous list can be a std::tuple with an additional operator[] and the return type is a special instantiation of std::variant which a) can store references instead of just values and b) once assigned cannot change its underlying type. b) is easy to achieve by using const std::variant because std::variant::operator= is a non-const member function. We might want to inherit from std::variant to create a new type with a new name that overloads operator= to be able to assign to the currently selected underlying type instead of changing the selection (I guess it would be really confusing to just add a const overload to std::variant::operator=, though technically it would work).

What I’m really missing from the proposed type is that it is always a reference to the heterogeneous list. In this way it is not possible for the user to choose between reference or copy. I think that this choice is really important. Back to a really simple example (I’m gonna use the STL types with features that don’t exist):
std::tuple<int,float> list{1,0.5f}
const std::variant<int,float> x = list[1]; // ‘1’ could also be a runtime value; make a copy of what is stored inside the list
const std::variant<int&,float&> y = list[0]; // get a reference to the object inside the list
const std::variant<const int&,const float&> z = list[1]; // now we have a reference, but cannot change the value inside the list
It would be helpful if std::variant<Ts&…> and std::variant<const Ts&…> would be convertible to std::variant<Ts…> (and std::variant<Ts…> to std::variant<const Ts…>).

Getting back to one of the previous examples:
int^ x = list[0];
I just noticed that this line could easily be written as
std::optional<int&> x = list[0];
This would allow us to write
*x = 2;
(if we are cocky and think we know that the optional has a value) or
x.value() = 2; // throws if x is nullopt

Sure, we could want nicer syntax than x.value(), but at least this simple example would work the way Muneem described it (or rather how I understood his description). (There was at least a proposal for std::optional<T&> at some point; but I’m not sure about its status.) If std::tuple::operator[] returns a const std::variant<Ts&…>, we might want to allow conversion from std::variant<Ts&…> to std::optional<T&> (where T is one of Ts…). The compiler can (or at least should) be able to see right through the optional if it knows it is not empty and optimize every access accordingly.

Here is how I would try to implement operator[] for std::tuple:
const std::variant<Ts&…> std::tuple<Ts&…>::operator[](std::size_t index) // also make the member function constexpr
{
    switch(index)
    {
    case 0:
        return Ts…[0];
        break;
    case 1:
        return Ts…[1];
        break;
    default:
        std::unreachable();
        break;
    }
}
We cannot write this by hand for every possible instantiation of the tuple, but I assume it is possible to use ’template for’ to fill the switch automatically based on the number of entries in the tuple. This would be just a couple of lines of code with reflection and no template metaprogramming (other than just the regular std::tuple stuff we already have).
Alternatively, we could write ‘return std::get<0>(*this);’ instead of ‘return Ts…[0];’ (I might have messed up the syntax because it is one of the newer features I haven’t used yet; probably needs to be a value and not a type).

This would make the proposal a short list:
1. Introduce std::variant<Ts&…>
2. Add operator[] to std::tuple which returns const std::variant<Ts&…>
3. Add conversion from std::variant<Ts&…> to std::optional<T&>
4. Implement lots of optimizations for std::variant and std::optional (and std::tuple::operator[] when used at compile time)

It can do (almost) anything requested. And the individual parts are not just for this one specific use case. Only the syntax does not look as nice.

--
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 2026-04-11 06:05:25