C++ Logo

std-proposals

Advanced search

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

From: Jan Schultke <janschultke_at_[hidden]>
Date: Fri, 19 Jun 2026 12:51:46 +0200
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
>

Received on 2026-06-19 10:52:03