Is it possible to allow a universal reference argument to be only called as const lvalue-ref or rvalue-ref, but not lvalue-ref?
Consider this:
#include <string>
#include <iostream>
struct S {
S() {}
S(const S&) {
std::cout << "copied" << std::endl;
}
S(S&&) {
std::cout << "moved" << std::endl;
}
};
auto foo(auto&& arg) {
return std::move(arg);
}
int main() {
std::cout << "temp call" << std::endl;
foo(S());
std::cout << std::endl << "variable call" << std::endl;
S s;
foo(s);
std::cout << std::endl << "move call" << std::endl;
foo(std::move(s));
}
This prints
temp call
moved
variable call
moved <--- should be copy here
move call
moved
I know that I should use std::forward
inside foo
when handling universal references:
auto foo(auto&& arg) {
return std::forward<decltype(arg)>(arg);
}
However, is it possible to enforce already in the signature of foo
that an argument ist either const T&
or T&&
, but never T&
?
Is it possible to allow a universal reference argument to be only called as const lvalue-ref or rvalue-ref, but not lvalue-ref?
Consider this:
#include <string>
#include <iostream>
struct S {
S() {}
S(const S&) {
std::cout << "copied" << std::endl;
}
S(S&&) {
std::cout << "moved" << std::endl;
}
};
auto foo(auto&& arg) {
return std::move(arg);
}
int main() {
std::cout << "temp call" << std::endl;
foo(S());
std::cout << std::endl << "variable call" << std::endl;
S s;
foo(s);
std::cout << std::endl << "move call" << std::endl;
foo(std::move(s));
}
This prints
temp call
moved
variable call
moved <--- should be copy here
move call
moved
I know that I should use std::forward
inside foo
when handling universal references:
auto foo(auto&& arg) {
return std::forward<decltype(arg)>(arg);
}
However, is it possible to enforce already in the signature of foo
that an argument ist either const T&
or T&&
, but never T&
?
2 Answers
Reset to default 6You might have overloads,
without template, it is simple, you just have
void foo(Object&&);
void foo(const Object&);
then foo(myObject)
would call void foo(const Object&);
With template, it is more complicated, as void foo(auto&&);
would match Object&
as well.
You might SFINAE it or use requires
(C++20)
template <typename T>
requires (!std::is_lvalue_reference_v<T>)
void foo(T&&);
template <typename T>
void foo(const T&);
or forward template function to template class
template <typename T>
struct Foo
{
void operator (T&&); // it is not a forwarding reference, T is fixed by the class already
void operator (const T&);
};
template <typename T>
void foo(T&& t)
{
Foo<std::decay_t<T>>{}(std::forward<T>(t));
}
or to template function, but with fixed T
// std::type_identity_t to guaranty the usage of foo_impl<Object>(..)
template <typename T>
void foo_impl(std::type_identity_t<T>&&);
template <typename T>
void foo_impl(const std::type_identity_t<T>&);
template <typename T>
void foo(T&& t)
{
foo_impl<std::decay_t<T>>(std::forward<T>(t));
}
In the presence of an overload that is a better match auto foo(auto&&)
cannot be called with a auto&
:
auto foo(auto&& arg) { return std::move(arg); }
auto foo(auto& arg) { return arg;}
Live Demo
const T&
? – NathanOliver Commented Mar 28 at 13:03foo
is of course more complicated with a lambda I want to forward the arg into. However, I find using std::forward somewhat error-prone, so I am wondering if I could just use std::move and enforce avoiding lvalue-ref by signature. – user3612643 Commented Mar 28 at 13:06T&
but example and output suggest you want to call it withT&
to enable a conditional move. Thats exactly whatstd::forward
was made for. If you encountered issues withstd::forward
you should better ask about that rather than trying to roll your own. – 463035818_is_not_an_ai Commented Mar 28 at 13:15c++14
tag but usingauto
as a parameter is not allowed. – edrezen Commented Mar 28 at 13:16