C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Don't use templates for differentiating Lvalues from Rvalues

From: Rhidian De Wit <rhidiandewit_at_[hidden]>
Date: Fri, 19 Jun 2026 20:29:17 +0200
I'm not entirely sure what the benefit of &&& would be over the current
forwarding reference? I get the sentiment you bring that using templates
just to resolve l-value references vs r-value references seems a bit of a
strange usage of templates, but IMO it makes perfect sense.
You are after all binding to different types, whether that is std::string,
std::string& or std::string&&. If you want to handle them separately, you
have the option to provide 3 separate overloads, but I think the downside
of &&& (teachability primarily) worsens it a lot more than the current
templating system.

Op vr 19 jun 2026 om 12:52 schreef Jan Schultke via Std-Proposals <
std-proposals_at_[hidden]>:

> Considering that e.g. the lvalue overload of push_back needs to call the
> copy constructor and the rvalue overload needs to call the move
> constructor, having either two distinct functions or a template feels like
> exactly the right way to express things.
>
> What you're proposing with &&& essentially has to be an abbreviated
> function template syntax, since you're suggesting that the compiler splits
> up your string&&& overloads into separate functions.
>
> Yours:
> void set(string &&&arg);
>
> Current (where forwarded is a concept):
> void set(forwarded<string> auto&& arg);
> // or
> void set(string arg);
> // or
> void set(const string& arg);
> void set(string&& arg);
>
> Admittedly, the one downside of the existing template approach is that
> there are two distinct instantiations for const lvalues and const rvalues,
> and maybe that is a problem worth solving using a const_as_lvalue keyword
> or whatever. I don't see substantial value in your much broader idea
> considering that all of the current approaches are more or less fine.
>
> On Fri, 19 Jun 2026 at 12:24, Frederick Virchanza Gotham via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
>
>> Templates were added to the C++ programming language so that we could
>> substitute a type. So if a container contained elements of type 'T',
>> we could set 'T' to 'int' or 'double' or 'std::string' or whatever. Or
>> if a sorting function sorted elements of 'T', then we could make 'T'
>> be whatever type we wanted.
>>
>> Later we decided that compile-time constant values could be
>> substituted, making it possible to do 'std::array<int, 1024u>'.
>>
>> I consider it to be a misuse of templates when a function is turned
>> into a template function for no other reason that to accommodate
>> Lvalues Vs Rvalues. What I mean is when the following class:
>>
>> class MyClass {
>> string s;
>> public:
>> void set(string &&arg) { s = move(arg); }
>> void set(string const & arg) { s = arg ; }
>> };
>>
>> gets re-written as:
>>
>> class MyClass {
>> string s;
>> public:
>> template<typename T>
>> requires std::is_same_v<T&, string&>
>> void set(T &&arg)
>> {
>> s = forward<T>(arg);
>> }
>> };
>>
>> What I would like to propose, is that we do away with this use of
>> templates in C++. I think instead it would be better if we placed a
>> marker on a function parameter to indicate that it will accept either
>> an Lvalue or an Rvalue. We can argue over syntax another day, but for
>> now I'll go with the tripple-character-token "&&&" (which is globbed
>> as one token). So the above class would become:
>>
>> class MyClass {
>> string s;
>> public:
>> void set(string &&&arg)
>> {
>> s = forward(arg);
>> }
>> };
>>
>> So the first potential problem that pops in is as follows: How do we
>> take the address of such a function? Here's my idea of how this would
>> be achieved, and for the sake of simplicity, let's talk about a normal
>> function rather than a member function, as follows:
>>
>> string s; // global variable
>>
>> void set(string &&&arg)
>> {
>> s = forward(arg);
>> }
>>
>> int main(void)
>> {
>> string monkey("monkey");
>> set(monkey);
>> }
>>
>> One possibility is that the compiler could implement this as three
>> separate functions as follows:
>>
>> string s; // global variable
>>
>> void set___L(string &arg)
>> {
>> s = arg;
>> }
>>
>> void set___R(string &&arg)
>> {
>> s = move(arg);
>> }
>>
>> void set(string *const ps, bool const is_rvalue)
>> {
>> if ( is_r_value )
>> return set___R(*ps,true );
>> else
>> return set___L(*ps,false);
>> }
>>
>> int main(void)
>> {
>> string monkey("monkey");
>> set(&monkey, false);
>> }
>>
>> So if you take the address of 'set', you're taking the address of the
>> function that does the branching -- therefore the Lvalue and Rvalue
>> forms of the function have the same address.
>>
>> The second potential problem that pops in is as follows: What about
>> the constness of the argument? So far I've given only one possibility:
>>
>> 1 - Parameter type 'string&&&' can be become either 'string&' or
>> 'string&&'
>>
>> But what if we want the parameter to be either 'string const &' or
>> 'string&&'. Well for that, I propose:
>>
>> 2 - Parameter type 'string const &&&' can be become either 'string
>> const &' or 'string&&'
>>
>> At first glance, you might ask why is the 'const' applied to the
>> Lvalue but not the Rvalue, and my answer is that there's no point in
>> accommodating an Rvalue if you can't plunder its resources, so there's
>> no point in accommodating an Rvalue if it's const.
>>
>> The third potential problem that pops in is as follows: What if the
>> function contains a static variable? For example:
>>
>> void set(string &&&arg)
>> {
>> static string var;
>> var = forward(arg);
>> }
>>
>> The neatest solution here is that you simply cannot have a 'static'
>> variable inside a function which has one or more parameter types
>> marked as '&&&'. Now we don't have to worry about there being one
>> instance of 'var' for every instantiation.
>>
>> These three problems are the only three that come to mind to me right now.
>>
>> If the function has multiple parameters such as:
>>
>> void set(string &&&a, vector<int> &&&b, vector<void*> &&&c);
>>
>> then the compiler can implement it as:
>>
>> void set(string *pa, bool is_rvalueA, vector<int> *pb, bool
>> is_rvalueB, vector<void*> *pc, is_rvalueC)
>> {
>> /**/ if ( !is_rvalueA && !is_rvalueB && !is_rvalueC )
>> set___RRR(*pa,*pb,*pc);
>> else if ( !is_rvalueA && !is_rvalueB && is_rvalueC )
>> set___RRL(*pa,*pb,*pc);
>> else if ( !is_rvalueA && is_rvalueB && !is_rvalueC )
>> set___RLR(*pa,*pb,*pc);
>> else if ( !is_rvalueA && is_rvalueB && is_rvalueC )
>> set___RLL(*pa,*pb,*pc);
>> else if ( is_rvalueA && !is_rvalueB && !is_rvalueC )
>> set___LRR(*pa,*pb,*pc);
>> else if ( is_rvalueA && !is_rvalueB && is_rvalueC )
>> set___LRL(*pa,*pb,*pc);
>> else if ( is_rvalueA && is_rvalueB && !is_rvalueC )
>> set___LLR(*pa,*pb,*pc);
>> else if ( is_rvalueA && is_rvalueB && is_rvalueC )
>> set___LLR(*pa,*pb,*pc);
>> }
>>
>> Now the guys who work in low-latency finance are looking at this
>> thinking: "That if-else ladder will slow my program down". Well, the
>> compiler has all the information it needs at compile-time to elide the
>> if-else ladder and just jump straight to the correct overload.
>>
>> This also solves another problem (or at least helps greatly with it):
>> Normally when we write a template function, we have to put the C++
>> code inside a header file. And so if the code is proprietary, the
>> whole world gets to see it. I think about 20 years ago the 'export'
>> keyword was supposed to solve this problem, but I don't think it ever
>> caught on. So normally the following function would need to go inside
>> a header file:
>>
>> template<typename T>
>> requires std::is_same_v<T&, string&>
>> void set(T &&arg)
>> {
>> s = forward<T>(arg); // proprietary code
>> }
>>
>> There is actually a way of putting this proprietary code inside a
>> source file, but it's not very nice, as you need to put the following
>> in the header file:
>>
>> template<typename T>
>> requires std::is_same_v<T&, string&>
>> void set(T &&arg);
>>
>> template<>
>> extern void set<string>(string &&arg);
>>
>> template<>
>> extern void set<string&>(string &arg);
>>
>> And then you put the following in the source file:
>>
>> template<>
>> void set<string>(string &&arg)
>> {
>> s = forward<string>(arg); // proprietary code
>> }
>>
>> template<>
>> void set<string&>(string &arg)
>> {
>> s = forward<string&>(arg); // proprietary code
>> }
>>
>> But now we have code duplication, as we have duplicated the function
>> body. The preprocessor could come to the rescue as follows:
>>
>> #define DEFINE_SET(token1,token2) \
>> template<> \
>> void set<string token1>(string token2 arg) \
>> { \
>> s = forward< string token1 >(arg); \
>> }
>>
>> DEFINE_SET( , && )
>> DEFINE_SET( &, & )
>>
>> But nobody really wants to do that (except for the guys at Boost maybe).
>>
>> With the feature I'm describing in this post, you would simply put the
>> following in the header file:
>>
>> extern void set(string const &&&arg);
>>
>> and then put the following in the source file:
>>
>> string s;
>>
>> void set(string const &&&arg)
>> {
>> s = forward(arg);
>> }
>>
>> Simple as that. So now we also have a neat way of transferring
>> proprietary code from the header file (where anyone can see it) to a
>> source file (which can be supplied as a compiled object file).
>>
>> A few people here might be thinking, "But what about the 'sink'
>> idiom?", as follows:
>>
>> // Header
>> class MyClass {
>> string s;
>> public:
>> void set(string arg);
>> };
>>
>> // Source file
>> void MyClass::set(string arg) {
>> s = std::move(arg);
>> }
>>
>> If an Rvalue is passed, it is moved into 'arg', then moved into 's'.
>> Can't really argue with that.
>> If an Lvalue is passed, it is copied into 'arg', then moved into 's',
>> but this could result in an unnecessary memory allocation if 's'
>> already had a big-enough buffer.
>>
>> I've never liked using template functions purely just to accommodate
>> Lvalues Vs Rvalues, and so this is one possible alternative.
>> --
>> 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
>


-- 
Rhidian De Wit
Software Engineer - Barco

Received on 2026-06-19 18:29:32