std::forward with templated overloaded function - templates

I've got no idea why compiler gives me warnings about template instantiations.
Thats a piece of code which runs just fine and outputs lvalue/rvalue properly:
//template<typename T>
void overloaded(const /*T*/std::string& in)
{
std::cout << "lvalue" << std::endl;
}
//template<typename T>
void overloaded(/*T*/std::string&& in)
{
std::cout << "rvalue" << std::endl;
}
template<typename T>
void pass(T&& in)
{
overloaded(std::forward<T>(in));
}
int main()
{
std::string a;
pass(a);
pass(std::move(a));
getchar();
}
But i need to use it with templated type. So modifying the "overloaded" functions to
template<typename T>
void overloaded(const T& in)
{
std::cout << "lvalue" << std::endl;
}
template<typename T>
void overloaded(T&& in)
{
std::cout << "rvalue" << std::endl;
}
Gives template instantiations warnings, (when to me its clear T should be std::string), and console outputs rvalue 2 times instead of lvalue first.
What am i doing wrong?

Templates such as T&& are special. They are called "forwarding references". They have special deduction rules for functions like:
template<typename T>
void overloaded(T&& in)
Assume for a moment that overloaded is not overloaded. If you pass an lvalue expression of type std::string to overloaded, T will deduce as std::string&. If you pass an rvalue expression of type std::string to overloaded, T will deduce to std::string. You can use this knowledge to do this:
template<typename T>
void overloaded(T&& in)
{
if (std::is_lvalue_reference<T>::value)
std::cout << "lvalue" << std::endl;
else
std::cout << "rvalue" << std::endl;
}
In general, it is an anti-pattern to overload T&& templates with anything else. These special templates come in handy when you want to catch everything.

Related

SFINAE Ambiguity

Looking to get some detailed explanations as to why the following is allowed / disallowed.
#include <type_traits>
#include <iostream>
template<typename T>
std::enable_if_t<std::is_arithmetic_v<T>> foo(T v) {
std::cout << "arithmetic version called\n";
}
template<typename T>
std::enable_if_t<!std::is_arithmetic_v<T>> foo(T v) {
std::cout << "non arithmetic version called\n";
}
int main() {
foo(33);
foo("hello");
}
In relation to the above code sample, why is this allowed as overload resolution would not kick in as the functions differ only by return type?
Following on from the above, with Expression SFINAE I have to make sure the i disambiguate the overloads as in the following code snippet.
#include <type_traits>
#include <iostream>
template<typename T, std::enable_if_t<std::is_arithmetic_v<T>>* p = nullptr>
void foo(T v) {
std::cout << "arithmetic version called\n";
}
template<typename T, std::enable_if_t<!std::is_arithmetic_v<T>>* p = nullptr>
void foo(T v) {
std::cout << "non arithmetic version called\n";
}
int main() {
foo(33);
foo("hello");
}
So I guess this question is more of a why it works, ie language rules vs how to do it. I have been using these techniques for a while and often still scratch my head on which bits of the languages rules enable this.
Thanks in advance.
#include <type_traits>
#include <iostream>
#include <string>
template<typename T>
auto increment_or_self_(T a, int i) -> std::decay_t<decltype(++std::declval<T&>(), std::declval<T>())> {
++a;
return a;
}
template<typename T>
auto increment_or_self_(T a, ...) -> T {
return a;
}
template<typename T>
T increment_or_self(T a) {
return increment_or_self_(a, 0);
}
int main() {
std::cout << increment_or_self(33) << std::endl;
std::cout << increment_or_self(std::string("hello")) << std::endl;
}
In the above increment_or_self has a dummy arg to ensure the overloads are not ambiguous.
Also why do we have to use pointers when we move the EnableIf as shown in the following?
Your first example has no problems with overload resolution, because you do not have two times the same function with different return types, because the SFINAE stuff already disables one of both in each of the instantiations. Even if your SFINAE expression will result in two different return types, it simply doesn't matter, because both instantiated functions will have different signatures as they have different input parameters.
I modified your example and this will also be well formed ( see different return types with this change )
std::enable_if_t<std::is_arithmetic_v<T>, float> foo(T ) {
std::cout << "arithmetic version called\n";
return 1.1;
}
template<typename T>
std::enable_if_t<!std::is_arithmetic_v<T>, int> foo(T ) {
std::cout << "non arithmetic version called\n";
return 1;
}
int main() {
foo(33);
foo("hello");
std::cout << std::is_same_v< float, decltype(foo(33))> << std::endl;
std::cout << std::is_same_v< int, decltype(foo("Hallo"))> << std::endl;
}
Your question: "Also why do we have to use pointers when we move the EnableIf as shown in the following?"
Quite simple: The std::enable_if with only one parameter ends in a void. And you simply can't assign a value to void. You can change your code to std::enable_if_t<!std::is_arithmetic_v<T>, bool> p = true as now your std::enable_if results in a bool where you can assign a value like true.
And with C++20 you can simplify a lot by using concepts
template <typename T> concept arithmetic = std::is_arithmetic_v<T>;
template< arithmetic T>
void foo(T) {
std::cout << "arithmetic version called\n";
}
void foo(auto) {
std::cout << "non arithmetic version called\n";
}
int main() {
foo(33);
foo("hello");
}
As you can see, we do not need to define a "non-arithmetic" concept, as we simply can trust on the rule, that the more specified template will be used, if more templates are able to be instantiated.
Note you didn't specify different return values, both functions return a void.
So there is no problem there. SFINAE will just ensure one of the functions will not result in a compilable return type (but will not give an error, since substition is not an error).
Don't get to focused on SFINAE, c++ keeps improving meta template programming techniques and in this case I would just use an if constexpr solution. Like this:
#include <type_traits>
#include <iostream>
template<typename T>
void foo(const T& v) // <== I prefer not to use pass by value for template parameters so I pass by reference (avoid copying)
{
if constexpr (std::is_arithmetic_v<T>)
{
std::cout << "arithmetic version called\n";
}
else
{
std::cout << "non arithmetic version called\n";
}
}
int main()
{
foo(33);
foo("hello");
return 0;
}

C++ variadic template function with move semantics

In C++, I have a set of variadic template functions that I'd like to accept an arbitrary number of parameters, either constant references or as r-value references (so I can move things rather than copy when possible). However, I find that the version that accepts the constant reference is called even when I'm wrapping arguments in std::move().
// Accept end of parameters.
void example () {}
// Accept a non-constant R-value reference to do a move
template <typename... More>
void example (std::string &&value, More... parameters) {
std::cout << value << ": moved" << std::endl;
example (parameters...);
}
// Accept a constant reference parameter.
template <typename... More>
void example (const std::string &value, More... parameters) {
std::cout << value << ": copied" << std::endl;
example (parameters...);
}
int main (int, char **) {
std::string first { "first" };
std::string second { "second" };
std::string third { "third" };
std::cout << "Trying variadic with move as second parameter: " << std::endl;
example (first, std::move (second), third);
// std::cout << "Trying variadic with move as first parameter: " << std::endl;
// This next line won't even compile when uncommented
// example (std::move (first), std::move (second), third);
return 0;
}
The output is:
Trying variadic with move as second parameter:
first: copied
second: copied
third: copied
instead of the expected:
Trying variadic with move as second parameter:
first: copied
second: moved
third: copied
And as a bonus, when I wrap the first argument in std::move(), I get a compile error on both g++7 and clang 9.
What am I doing wrong?
There are several problems here:
More... parameters always receives arguments by value (as long as the template parameters are deduced), because types in typename ...More will never be deduced as references.
All arguments passed to example in example(parameters...); will always be lvalues.
The string && overload can't call the const string & one, because it's not yet declared at that point.
Instead of passing by value, you should use forwarding references and std::forward. And you need to declare the const string & overload before defining the string && one.
void example() {}
// Declare lvalue overload.
template <typename ...More>
void example(const std::string &value, More &&... parameters);
// Rvalue overload.
template <typename... More>
void example (std::string &&value, More &&... parameters) {
std::cout << value << ": moved" << std::endl;
example(std::forward<More>(parameters)...);
}
// Lvalue overload.
template <typename ...More>
void example(const std::string &value, More &&... parameters) {
std::cout << value << ": copied" << std::endl;
example(std::forward<More>(parameters)...);
}
This works:
void example2 () {}
template<typename First, typename... More>
void example2( First &&value, More&&... parameters ) {
if constexpr( std::is_rvalue_reference_v<decltype(value)> ) {
std::cout << value << ": moved" << std::endl;
}
else {
std::cout << value << ": copied" << std::endl;
}
example2( std::forward<More>(parameters)... );
}
int main (int, char **) {
std::string first { "first" };
std::string second { "second" };
std::string third { "third" };
std::cout << "copy, move, copy: " << std::endl;
example2( first, std::move(second), third );
// if we really moved second in the previous call
// then second will be an empty string here.
std::cout << "move, move, copy: " << std::endl;
example2(std::move(first), std::move(second), third);
return 0;
}
If you don't forward parameters any && will be passed as &. This is the main issue with your attempt.
You also don't need two functions to handle value, if you make it a && template param it will handle both cases: lvalue and rvalue, which we can detect using is_rvalue_reference<>.
HolyBlackCat already mentioned why your code behaves as it does so I'm not going to explain that again here. Here is another solution using fold expressions:
template <typename T>
void process(T const& lvalue)
{
std::cout << lvalue << ": copied\n";
}
// the enable_if is necessary because T&& is a forwarding reference, not a rvalue reference
template <typename T, typename = std::enable_if_t<!std::is_lvalue_reference_v<T>>>
void process(T&& rvalue)
{
std::cout << rvalue << ": moved\n";
}
template <typename... Args>
void example(Args&&... args)
{
(process(std::forward<Args>(args)), ...);
}
Example

Why named variable calls are resolved to T&& instead of const T&?

As the title says, why named variables calls are resolved to T&& instead of const T& functions?
#include <iostream>
template<typename T>
void f(T&& v)
{
std::cout << "void f(T&& v)" << std::endl;
}
template<typename T>
void f(const T& v)
{
std::cout << "void f(const T& v)" << std::endl;
}
int main()
{
int i = 0;
f(1);
f(i);
}
In this case both calls are resolved to first version of f(), even if i is named. One solution would be to add also:
template<typename T>
void f(T& v)
{
std::cout << "void f(T& v)" << std::endl;
}
or to change first version to:
template<typename T>
typename std::enable_if<!std::is_reference<T>::value, void>::type f(T&& v)
{
std::cout << "void f(T&& v)" << std::endl;
}
but I want to understand the reasons behind this decision.
The deduction is T = int &, which means f(T &&) == f(int &). The overload resolution rules ([over.ics.rank/13.3.3.2]) say that this is is a strictly better match than f(int const &). Both are classified as an "exact match" (binding a value to a reference), but the less CV-qualified reference is preferred.
A less standardese-encumbered answer would be to recognise that in the declaration:
template<typename T>
void f(T&& v)
Because T is a deduced type, T&& v is what Scott Meyers calls a universal reference, which can bind to both rvalues and lvalues. T can be deduced to be a reference type, so you are actually calling f<int&>(int& && v), at which point the reference-collapsing rules come into effect, making the nominal signature of this function f<int&>(int& v) which, as the previous answer noted, is a better match for a non-const int argument.
In Meyers' upcoming Effective Modern C++ he has the following items:
Item 30: Pass and return rvalue references via std::move, universal references via std::forward.
Item 31: Avoid overloading on universal references.
Item 31 tells you what not to do. Item 30 suggests what you could do instead, write only one overload of f, and perfectly forward v with std::forward:
template<typename T>
void f(T&& v)
{
std::cout << "void f(T&& v)" << std::endl;
if (std::is_rvalue_reference<decltype(v)>::value)
std::cout << "rvalue" << std::endl;
else
std::cout << "lvalue" << std::endl;
std::cout << "v = " << std::forward<T>(v) << std::endl;
}

Why doesn't overloaded function bind to more specific overload?

Consider the following overloaded functions:
template <class T>
void foo(const T& v)
{
std::cout << "Generic version" << std::endl;
}
void foo(std::pair<const void*, std::size_t> p)
{
std::cout << "Pair version" << std::endl;
}
Below, I expect the second overload (the one that takes an std::pair) to be called:
int main()
{
const void* buf = 0;
std::size_t sz = 0;
foo(std::make_pair(buf, sz));
}
However, this code in fact calls the generic version. Why doesn't it bind to the overload that specifically takes an std::pair? Is this a compiler bug? I'm using a pretty old compiler, GCC 4.1.2
You need to declare your specialized function as a template
Your specialized argument type must follow the template parameter (i.e. be a const reference) as well.
Try
template <>
void foo(const std::pair<const void*, std::size_t>& p)
{
...
}

Preventing non-const lvalues from resolving to rvalue reference instead of const lvalue reference

I'm having trouble overloading a function to take a value either by const reference or, if it is an rvalue, an rvalue reference. The problem is that my non-const lvalues are binding to the rvalue version of the function. I'm doing this in VC2010.
#include <iostream>
#include <vector>
using namespace std;
template <class T>
void foo(const T& t)
{cout << "void foo(const T&)" << endl;}
template <class T>
void foo(T&& t)
{cout << "void foo(T&&)" << endl;}
int main()
{
vector<int> x;
foo(x); // void foo(T&&) ?????
foo(vector<int>()); // void foo(T&&)
}
The priority seems to be to deduce foo(x) as
foo< vector<int> & >(vector<int>& && t)
instead of
foo< vector<int> >(const vector<int>& t)
I tried replacing the rvalue-reference version with
void foo(typename remove_reference<T>::type&& t)
but this only had the effect of causing everything to resolve to the const-lvalue reference version.
How do I prevent this behaviour? And why is this the default anyway - it seems so dangerous given that rvalue-references are allowed to be modified, this leaves me with an unexpectedly modified local variable.
EDIT: Just added non-template versions of the functions, and they work as expected. Making the function a template changes the overload resolution rules? That is .. really frustrating!
void bar(const vector<int>& t)
{cout << "void bar(const vector<int>&)" << endl;}
void bar(vector<int>&& t)
{cout << "void bar(vector<int>&&)" << endl;}
bar(x); // void bar(const vector<int>&)
bar(vector<int>()); // void bar(vector<int>&&)
When you have a templated function like this you almost never want to overload. The T&& parameter is a catch anything parameter. And you can use it to get any behavior you want out of one overload.
#include <iostream>
#include <vector>
using namespace std;
template <class T>
void display()
{
typedef typename remove_reference<T>::type Tr;
typedef typename remove_cv<Tr>::type Trcv;
if (is_const<Tr>::value)
cout << "const ";
if (is_volatile<Tr>::value)
cout << "volatile ";
std::cout << typeid(Trcv).name();
if (is_lvalue_reference<T>::value)
std::cout << '&';
else if (is_rvalue_reference<T>::value)
std::cout << "&&";
std::cout << '\n';
}
template <class T>
void foo(T&& t)
{
display<T>();
}
int main()
{
vector<int> x;
vector<int> const cx;
foo(x); // vector<int>&
foo(vector<int>()); // vector<int>
foo(cx); // const vector<int>&
}
In order for T&& to bind to an lvalue reference, T must itself be an lvalue reference type. You can prohibit the template from being instantiated with a reference type T:
template <typename T>
typename std::enable_if<!std::is_reference<T>::value>::type foo(T&& t)
{
cout << "void foo(T&&)" << endl;
}
enable_if is found in <utility>; is_reference is found in <type_traits>.
The reason that the overload taking T&& is preferred over the overload taking a T const& is that T&& is an exact match (with T = vector<int>&) but T const& requires a qualification conversion (const-qualification must be added).
This only happens with templates. If you have a nontemplate function that takes a std::vector<int>&&, you will only be able to call that function with an rvalue argument. When you have a template that takes a T&&, you should not think of it as "an rvalue reference parameter;" it is a "universal reference parameter" (Scott Meyers used similar language, I believe). It can accept anything.
Allowing a T&& parameter of a function template to bind to any category of argument is what enables perfect forwarding.