何时使用 std::forward 转发参数?

发布于 2024-12-02 10:10:57 字数 1186 浏览 2 评论 0原文

C++0x 显示了使用 std::forward 的示例:

template<class T>
void foo(T&& arg) 
{
  bar(std::forward<T>(arg));
}

什么时候总是使用 std::forward 有利?

另外,它要求在参数声明中使用 && ,它在所有情况下都有效吗?我认为如果函数是用 && 声明的,则必须将临时变量传递给函数,那么可以使用任何参数调用 foo 吗?

最后,如果我有一个像这样的函数调用:

template<int val, typename... Params>
void doSomething(Params... args) {
  doSomethingElse<val, Params...>(args...);
}

我应该使用这个来代替:

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
}

另外,如果在函数中使用参数两次,即同时转发到两个函数,那么使用 std:: 是否明智?转发? std::forward 是否会将同一事物两次转换为临时变量,从而移动内存并使其在第二次使用时无效?下面的代码可以吗:

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
  doSomethingWeird<val, Params...>(std::forward<Params>(args)...);
}

我对 std::forward 有点困惑,我很乐意进行一些清理。

C++0x shows an example of using std::forward:

template<class T>
void foo(T&& arg) 
{
  bar(std::forward<T>(arg));
}

When is it advantageous to use std::forward, always?

Also, it requires to use && in the parameters declaration, is it valid in all cases? I thought you had to pass temporaries to a function if the function was declared with && in it, so can foo be called with any parameter?

Lastly, if I have a function call such as this:

template<int val, typename... Params>
void doSomething(Params... args) {
  doSomethingElse<val, Params...>(args...);
}

Should I use this instead:

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
}

Also, if use the parameters twice in the function, i.e. forwarding to two functions at the same time, is it wise to use std::forward? Won't std::forward convert the same thing to a temporary twice, moving the memory and make it invalid for a second use? Would the following code be ok:

template<int val, typename... Params>
void doSomething(Params&&... args) {
  doSomethingElse<val, Params...>(std::forward<Params>(args)...);
  doSomethingWeird<val, Params...>(std::forward<Params>(args)...);
}

I'm a bit confused by std::forward, and I'd gladly use some clearing up.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

厌味 2024-12-09 10:10:57

像第一个示例一样使用它:

template <typename T> void f(T && x)
{
  g(std::forward<T>(x));
}

template <typename ...Args> void f(Args && ...args)
{
  g(std::forward<Args>(args)...);
}

这是因为 引用折叠规则:如果 T = U&,则 T&& = U&,但如果 T = U&&,则 T&& = U&&,因此您始终会在函数体内得到正确的类型。最后,您需要 forward 将左值转换的 x (因为它现在有了名称!)返回到右值引用(如果它最初是右值引用)。

但是,您不应多次转发某些内容,因为这通常没有意义:转发意味着您可能参数一直移动到最终调用者,一旦移动,它就消失了,因此您不能再次使用它(以您可能想要的方式)。

Use it like your first example:

template <typename T> void f(T && x)
{
  g(std::forward<T>(x));
}

template <typename ...Args> void f(Args && ...args)
{
  g(std::forward<Args>(args)...);
}

That's because of the reference collapsing rules: If T = U&, then T&& = U&, but if T = U&&, then T&& = U&&, so you always end up with the correct type inside the function body. Finally, you need forward to turn the lvalue-turned x (because it has a name now!) back into an rvalue reference if it was one initially.

You should not forward something more than once however, because that usually does not make sense: Forwarding means that you're potentially moving the argument all the way through to the final caller, and once it's moved it's gone, so you cannot then use it again (in the way you probably meant to).

聽兲甴掵 2024-12-09 10:10:57

Kerrek的回答非常有用,但它并没有完全回答标题中的问题:

何时使用 std::forward 转发参数?

为了回答这个问题,我们首先应该引入一个概念 通用参考。斯科特·迈耶斯(Scott Meyers)给出了这个名字,现在它们通常被称为转发参考。基本上,当您看到类似这样的内容时:

template<typename T>
void f(T&& param);

请记住,param 不是右值引用(人们可能会想得出这样的结论),而是通用引用*。通用引用的特点是非常受限制的形式(只有 T&&,没有 const 或类似的限定符)和类型推导 - 类型 T 将在调用 f 时推导。简而言之,通用引用对应于右值引用,如果它们是用
右值,以及左值引用(如果它们是用左值初始化的)。

现在回答最初的问题相对容易 - 将 std::forward 应用于:

  • 上次在函数中使用时的通用引用
  • 从按值返回的函数返回的通用

引用 的示例第一种情况:

template<typename T>
void foo(T&& prop) {
    other.set(prop); // use prop, but don't modify it because we still need it
    bar(std::forward<T>(prop)); // final use -> std::forward
}

在上面的代码中,我们不希望 propother.set(..) 完成后有一些未知的值,因此这里不会发生转发。然而,当调用 bar 时,我们会在完成后转发 prop,并且 bar 可以用它做任何它想做的事情(例如移动它)。

第二种情况的示例:

template<typename T>
Widget transform(T&& prop) {
   prop.transform();
   return std::forward<T>(prop);
}

如果它是右值,则此函数模板应将 prop 移动到返回值中,如果它是左值,则应复制它。如果我们在最后省略了 std::forward,我们总是会创建一个副本,当 prop 恰好是右值时,它的成本会更高。

*准确地说,通用引用是对 cv 不合格模板参数进行右值引用的概念。

Kerrek's answer is very useful, but it doesn't completely answer the question from the title:

When to use std::forward to forward arguments?

In order to answer it, we should first introduce a notion of universal references. Scott Meyers gave this name and nowadays they are often called forwarding references. Basically, when you see something like this:

template<typename T>
void f(T&& param);

bear in mind that param is not an rvalue reference (as one may be tempted to conclude), but a universal reference*. Universal references are characterized by a very restricted form (just T&&, without const or similar qualifiers) and by type deduction - the type T will be deduced when f is invoked. In a nutshell, universal references correspond to rvalue references if they’re initialized with
rvalues, and to lvalue references if they’re initialized with lvalues.

Now it's relatively easy to answer the original question - apply std::forward to:

  • a universal reference the last time it’s used in the function
  • a universal reference being returned from functions that return by value

An example for the first case:

template<typename T>
void foo(T&& prop) {
    other.set(prop); // use prop, but don't modify it because we still need it
    bar(std::forward<T>(prop)); // final use -> std::forward
}

In the code above, we don't want prop to have some unknown value after other.set(..) has finished, so no forwarding happens here. However, when calling bar we forward prop as we are done with it and bar can do whatever it wants with it (e.g. move it).

An example for the second case:

template<typename T>
Widget transform(T&& prop) {
   prop.transform();
   return std::forward<T>(prop);
}

This function template should move prop into the return value if it's an rvalue and copy it if it's an lvalue. In case that we omitted std::forward at the end, we would always create a copy, which is more expensive when prop happens to be an rvalue.

*to be fully precise, a universal reference is a concept of taking an rvalue reference to a cv-unqualified template parameter.

给我一枪 2024-12-09 10:10:57

这个例子有帮助吗?我努力寻找 std::forward 的有用的非通用示例,但偶然发现了我们传递的银行帐户的示例
作为参数存入的现金。

因此,如果我们有一个帐户的 const 版本,当我们将其传递给我们的存款模板时,我们应该期望它会发生变化。调用 const 函数;然后抛出一个异常(这个想法是这是一个锁定的帐户!)

如果我们有一个非常量帐户,那么我们应该能够修改该帐户。

#include <iostream>
#include <string>
#include <sstream> // std::stringstream
#include <algorithm> // std::move
#include <utility>
#include <iostream>
#include <functional>

template<class T> class BankAccount {
private:
    const T no_cash {};
    T cash {};
public:
    BankAccount<T> () {
        std::cout << "default constructor " << to_string() << std::endl;
    }
    BankAccount<T> (T cash) : cash (cash) {
        std::cout << "new cash " << to_string() << std::endl;
    }
    BankAccount<T> (const BankAccount& o) {
        std::cout << "copy cash constructor called for " << o.to_string() << std::endl;
        cash = o.cash;
        std::cout << "copy cash constructor result is  " << to_string() << std::endl;
    }
    // Transfer of funds?
    BankAccount<T> (BankAccount<T>&& o) {
        std::cout << "move cash called for " << o.to_string() << std::endl;
        cash = o.cash;
        o.cash = no_cash;
        std::cout << "move cash result is  " << to_string() << std::endl;
    }
    ~BankAccount<T> () {
        std::cout << "delete account " << to_string() << std::endl;
    }
    void deposit (const T& deposit) {
        cash += deposit;
        std::cout << "deposit cash called " << to_string() << std::endl;
    }
    friend int deposit (int cash, const BankAccount<int> &&account) {
        throw std::string("tried to write to a locked (const) account");
    }
    friend int deposit (int cash, const BankAccount<int> &account) {
        throw std::string("tried to write to a locked (const) account");
    }
    friend int deposit (int cash, BankAccount<int> &account) {
        account.deposit(cash);
        return account.cash;
    }
    friend std::ostream& operator<<(std::ostream &os, const BankAccount<T>& o) {
        os << "$" << std::to_string(o.cash);
        return os;
    }
    std::string to_string (void) const {
        auto address = static_cast<const void*>(this);
        std::stringstream ss;
        ss << address;
        return "BankAccount(" + ss.str() + ", cash $" + std::to_string(cash) + ")";
    }
};

template<typename T, typename Account>
int process_deposit(T cash, Account&& b) {
    return deposit(cash, std::forward<Account>(b));
}

int main(int, char**)
{
    try {
        // create account1 and try to deposit into it
        auto account1 = BankAccount<int>(0);
        process_deposit<int>(100, account1);
        std::cout << account1.to_string() << std::endl;
        std::cout << "SUCCESS: account1 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account1 deposit failed!: " << e << std::endl;
    }

    try {
        // create locked account2 and try to deposit into it; this should fail
        const auto account2 = BankAccount<int>(0);
        process_deposit<int>(100, account2);
        std::cout << account2.to_string() << std::endl;
        std::cout << "SUCCESS: account2 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account2 deposit failed!: " << e << std::endl;
    }

    try {
        // create locked account3 and try to deposit into it; this should fail
        auto account3 = BankAccount<int>(0);
        process_deposit<int>(100, std::move(account3));
        std::cout << account3.to_string() << std::endl;
        std::cout << "SUCCESS: account3 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account3 deposit failed!: " << e << std::endl;
    }
}

构建:

cd std_forward
rm -f *.o example
c++ -std=c++2a -Werror -g -ggdb3 -Wall -c -o main.o main.cpp
c++ main.o  -o example
./example

预期输出:

# create account1 and try to deposit into it
new cash BankAccount(0x7ffee68d96b0, cash $0)
deposit cash called BankAccount(0x7ffee68d96b0, cash $100)
BankAccount(0x7ffee68d96b0, cash $100)
# SUCCESS: account1 deposit succeeded!
delete account BankAccount(0x7ffee68d96b0, cash $100)

# create locked account2 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9670, cash $0)
delete account BankAccount(0x7ffee68d9670, cash $0)
# FAILED: account2 deposit failed!: tried to write to a locked (const) account

# create locked account3 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9630, cash $0)
delete account BankAccount(0x7ffee68d9630, cash $0)
# FAILED: account3 deposit failed!: tried to write to a locked (const) account

Does this example help? I struggled to find a useful non generic example of std::forward, but hit upon an example of a bank account that we pass along
the cash to be deposited as an argument.

So if we have a const version of an account we should expect when we pass it to our deposit template<> that the const function is called; and this then throws an exception (the idea being this was a locked account!)

If we have a non const account then we should be able to modify the account.

#include <iostream>
#include <string>
#include <sstream> // std::stringstream
#include <algorithm> // std::move
#include <utility>
#include <iostream>
#include <functional>

template<class T> class BankAccount {
private:
    const T no_cash {};
    T cash {};
public:
    BankAccount<T> () {
        std::cout << "default constructor " << to_string() << std::endl;
    }
    BankAccount<T> (T cash) : cash (cash) {
        std::cout << "new cash " << to_string() << std::endl;
    }
    BankAccount<T> (const BankAccount& o) {
        std::cout << "copy cash constructor called for " << o.to_string() << std::endl;
        cash = o.cash;
        std::cout << "copy cash constructor result is  " << to_string() << std::endl;
    }
    // Transfer of funds?
    BankAccount<T> (BankAccount<T>&& o) {
        std::cout << "move cash called for " << o.to_string() << std::endl;
        cash = o.cash;
        o.cash = no_cash;
        std::cout << "move cash result is  " << to_string() << std::endl;
    }
    ~BankAccount<T> () {
        std::cout << "delete account " << to_string() << std::endl;
    }
    void deposit (const T& deposit) {
        cash += deposit;
        std::cout << "deposit cash called " << to_string() << std::endl;
    }
    friend int deposit (int cash, const BankAccount<int> &&account) {
        throw std::string("tried to write to a locked (const) account");
    }
    friend int deposit (int cash, const BankAccount<int> &account) {
        throw std::string("tried to write to a locked (const) account");
    }
    friend int deposit (int cash, BankAccount<int> &account) {
        account.deposit(cash);
        return account.cash;
    }
    friend std::ostream& operator<<(std::ostream &os, const BankAccount<T>& o) {
        os << "$" << std::to_string(o.cash);
        return os;
    }
    std::string to_string (void) const {
        auto address = static_cast<const void*>(this);
        std::stringstream ss;
        ss << address;
        return "BankAccount(" + ss.str() + ", cash $" + std::to_string(cash) + ")";
    }
};

template<typename T, typename Account>
int process_deposit(T cash, Account&& b) {
    return deposit(cash, std::forward<Account>(b));
}

int main(int, char**)
{
    try {
        // create account1 and try to deposit into it
        auto account1 = BankAccount<int>(0);
        process_deposit<int>(100, account1);
        std::cout << account1.to_string() << std::endl;
        std::cout << "SUCCESS: account1 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account1 deposit failed!: " << e << std::endl;
    }

    try {
        // create locked account2 and try to deposit into it; this should fail
        const auto account2 = BankAccount<int>(0);
        process_deposit<int>(100, account2);
        std::cout << account2.to_string() << std::endl;
        std::cout << "SUCCESS: account2 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account2 deposit failed!: " << e << std::endl;
    }

    try {
        // create locked account3 and try to deposit into it; this should fail
        auto account3 = BankAccount<int>(0);
        process_deposit<int>(100, std::move(account3));
        std::cout << account3.to_string() << std::endl;
        std::cout << "SUCCESS: account3 deposit succeeded!" << std::endl;
    } catch (const std::string &e) {
        std::cerr << "FAILED: account3 deposit failed!: " << e << std::endl;
    }
}

To build:

cd std_forward
rm -f *.o example
c++ -std=c++2a -Werror -g -ggdb3 -Wall -c -o main.o main.cpp
c++ main.o  -o example
./example

Expected output:

# create account1 and try to deposit into it
new cash BankAccount(0x7ffee68d96b0, cash $0)
deposit cash called BankAccount(0x7ffee68d96b0, cash $100)
BankAccount(0x7ffee68d96b0, cash $100)
# SUCCESS: account1 deposit succeeded!
delete account BankAccount(0x7ffee68d96b0, cash $100)

# create locked account2 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9670, cash $0)
delete account BankAccount(0x7ffee68d9670, cash $0)
# FAILED: account2 deposit failed!: tried to write to a locked (const) account

# create locked account3 and try to deposit into it; this should fail
new cash BankAccount(0x7ffee68d9630, cash $0)
delete account BankAccount(0x7ffee68d9630, cash $0)
# FAILED: account3 deposit failed!: tried to write to a locked (const) account
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文