Use the advantages of moving forward

In an ideal forward, std::forward is used to convert named right value references t1 and t2 to unnamed right value references. What is the purpose of this? If t1 and t2 are left, how does this affect the inner of the called function?

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

#1 building

I think there's a conceptual code that implements STD:: forward to increase the scope of the discussion. This is what Scott Meyers said In "Effective C ++ 11/14 samplers" One slide

The function move in the code is std::move. There is an implementation ahead of the presentation. I At libstdc++ Found in the move.h file in The actual implementation of STD:: forward But it's not inspiring at all.

From the user's point of view, it means that std::forward is conditionally cast to the right value. This is useful if I write a function that expects left or right values in its arguments, and only if you pass it as a right value, you want to pass it as a right value to another function. If I don't wrap the parameter in std::forward, it will always be passed as a regular reference.

#include <iostream>
#include <string>
#include <utility>

void overloaded_function(std::string& param) {
  std::cout << "std::string& version" << std::endl;
}
void overloaded_function(std::string&& param) {
  std::cout << "std::string&& version" << std::endl;
}

template<typename T>
void pass_through(T&& param) {
  overloaded_function(std::forward<T>(param));
}

int main() {
  std::string pes;
  pass_through(pes);
  pass_through(std::move(pes));
}

Sure enough, it will print

std::string& version
std::string&& version

This code is based on the example in the previous presentation. Slide 10 from about 15:00.

#2 building

What is not clear is that const T & > can also be handled correctly by static < T & & >.
Procedure:

#include <iostream>

using namespace std;

void g(const int&)
{
    cout << "const int&\n";
}

void g(int&)
{
    cout << "int&\n";
}

void g(int&&)
{
    cout << "int&&\n";
}

template <typename T>
void f(T&& a)
{
    g(static_cast<T&&>(a));
}

int main()
{
    cout << "f(1)\n";
    f(1);
    int a = 2;
    cout << "f(a)\n";
    f(a);
    const int b = 3;
    cout << "f(const b)\n";
    f(b);
    cout << "f(a * b)\n";
    f(a * b);
}

Generation:

f(1)
int&&
f(a)
int&
f(const b)
const int&
f(a * b)
int&&

Note that "F" must be a template function. This method is not valid if you only define it as void f (int & & A).

#3 building

If t1 and t2 are left, how does this affect the interior of the called function?

If t1 is of type char and t2 is a class after instantiation, you want to pass each replica t1 and each const reference t2. Well, unless every non const reference in inner() accepts them, that is, in this case, you do the same.

An attempt was made to write a set of outer() functions that do this without a right value reference, inferring the correct way to pass parameters from the inner() type. I think you will need 2 ^ 2 things, a lot of template metadata to infer parameters, and a lot of time to do that in all cases.

Then someone brought in inner(), which takes parameters for each pointer. I think it's 3 ^ 2 now. (or 4 ^ 2. Damn it, I don't have to worry about whether the const pointer will make a difference.)

Then imagine that you want to do this for five parameters. Or seven.

Now that you know why there are some clever ideas for "perfect forwarding": it lets the compiler do all this for you.

#4 building

You must understand the forwarding issue. You can Read the whole question in detail But I will summarize.

Basically, given the expression e (a, B,..., c), we want the expression f (a, B,..., c) to be equivalent. In C ++ 03, this is not possible. Tried a lot, but failed in some ways.

The easiest way to do this is to use an lvalue reference:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c)
{
    E(a, b, c);
}

But this cannot handle temporary values: f(1, 2, 3);, because these cannot be bound to left value references.

The next attempt might be:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(a, b, c);
}

The above problems can be solved, but triggers will appear. Now, it does not allow E to have a non constant parameter:

int i = 1, j = 2, k = 3;
void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these

The third attempt is to accept const references, but const_cast moves const away:

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c)
{
    E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c));
}

This can accept all values, pass all values, but can result in undefined behavior:

const int i = 1, j = 2, k = 3;
E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!

The final solution can handle all problems correctly, but at the cost of being unable to maintain them. Use all combinations of const and non const to provide f overloading:

template <typename A, typename B, typename C>
void f(A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, C& c);

template <typename A, typename B, typename C>
void f(const A& a, B& b, const C& c);

template <typename A, typename B, typename C>
void f(A& a, const B& b, const C& c);

template <typename A, typename B, typename C>
void f(const A& a, const B& b, const C& c);

N parameters need 2n combinations, which is a nightmare. We want to do this automatically.

(this is actually what the compiler does for us in C ++ 11.)

In C ++ 11, we have the opportunity to solve this problem. One solution modifies existing types of template inference rules, but this can break a lot of code. So we have to find another way.

The solution is to use the newly added rvalue references instead; we can introduce new rules when we derive the right value reference type and create any desired results. After all, we can't break code right now.

If you give a reference to a reference (note that a reference is a general term that includes T & and T &), we will use the following rules to calculate the result type:

"Given type T, which references type T, trying to create type" left reference to cv TR "creates type" left reference to t ", while trying to create right reference to type T" cv TR "creates TR type."

Or in tabular form:

TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

Next, use the template parameter derivation: if the parameter is A left value A, then provide A left value reference to A for the template parameter. Otherwise, we usually make inferences. This gives the so-called general reference (term“ Forward reference " Now it's official Reference resources ).

Why does this work? Because of the combination, we can track the value category of the type: if it is left value, there is a left value reference parameter, otherwise there is a right value reference parameter.

In the code:

template <typename T>
void deduce(T&& x); 

int i;
deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&)
deduce(1); // deduce<int>(int&&)

The last thing is the value category of the forward variable. Remember that once inside the function, the parameter can be passed as an lvalue to any object:

void foo(int&);

template <typename T>
void deduce(T&& x)
{
    foo(x); // fine, foo can refer to x
}

deduce(1); // okay, foo operates on x which has a value of 1

Bad E needs to get the same value category as we get! The solution is as follows:

static_cast<T&&>(x);

What is this for? Consider that we are inside the reduce function and have passed an left value. This means that T is a &, so the target type of static type conversion is a & &, or just a &. Since x is already a &, we do nothing but have an lvalue reference.

When we pass A right value, T is A, so the target type of static type conversion is A & &. Casting produces A right value expression that can no longer be passed to A left value reference. We maintain the value category of the parameter.

Putting them together makes us "perfect to forward":

template <typename A>
void f(A&& a)
{
    E(static_cast<A&&>(a)); 
}

When f receives an left value, E gets an left value. When f receives a right value, E gets a right value. Perfect.

Of course, we need to get rid of the ugly situation. Static_cast < T & & > is obscure and hard to remember; let's create a utility function named forward instead, which performs the same operation:

std::forward<A>(a);
// is the same as
static_cast<A&&>(a);

#5 building

In an ideal forward, STD:: forward is used to convert named right value references t1 and t2 to unnamed right value references. What is the purpose of this? If t1 and t2 are left, what is the effect on the inside of the called function?

template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }

If you use a named right value reference in an expression, it is actually a left value (because you reference objects by name). Consider the following example:

void inner(int &,  int &);  // #1
void inner(int &&, int &&); // #2

Now, if we call outer that

outer(17,29);

We want to forward 17 and 29 to 2, because 17 and 29 are integer literals and are such right values. But because T1 and t2 inner(t1,t2) in the expression inner(t1,t2); are left values, you will call 1 instead of 2. That's why we need to use std::forward to convert references back to unnamed references. Therefore, outer t1 is always a left value expression, while forward < T1 > (T1) may be a right value expression depending on T1. If T1 is an lvalue reference, the latter is just an lvalue expression. Also, if the first parameter to external is an lvalue expression, only T1 is derived as the lvalue reference.

Posted on Sun, 02 Feb 2020 23:54:29 -0500 by geo__