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);// orvoid set(string arg);// orvoid 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@lists.isocpp.org> 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@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