Quando usare std :: forward per inoltrare argomenti?


155

C ++ 0x mostra un esempio di utilizzo std::forward:

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

Quando è vantaggioso da usare std::forward, sempre?

Inoltre, deve essere utilizzato &&nella dichiarazione dei parametri, è valido in tutti i casi? Pensavo che dovessi passare dei provvisori a una funzione se la funzione è stata dichiarata con &&essa, quindi puoi chiamare con qualsiasi parametro?

Infine, se ho una chiamata di funzione come questa:

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

Dovrei usare questo invece:

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

Inoltre, se si utilizzano due volte i parametri nella funzione, ovvero l'inoltro a due funzioni contemporaneamente, è consigliabile utilizzarlo std::forward? Non std::forwardconvertirai la stessa cosa in temporanea due volte, spostando la memoria e rendendola non valida per un secondo utilizzo? Il seguente codice sarebbe ok:

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

Sono un po 'confuso std::forwarde vorrei usare volentieri qualche chiarimento.

Risposte:


124

Usalo come il tuo primo esempio:

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

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

Ciò è dovuto alle regole di compressione di riferimento : If T = U&, then T&& = U&, but if T = U&&, then T&& = U&&, quindi si finisce sempre con il tipo corretto all'interno del corpo della funzione. Infine, devi forwardtrasformare lvalue-turn x(perché ora ha un nome!) In un riferimento rvalue se inizialmente era uno.

Tuttavia, non dovresti inoltrare qualcosa più di una volta, perché di solito non ha senso: inoltro significa che stai potenzialmente spostando l'argomento fino al chiamante finale, e una volta spostato è sparito, quindi non puoi quindi usarlo di nuovo (nel modo in cui probabilmente intendevi).


Ho pensato che fosse Args...&& args?
Cucciolo

5
@DeadMG: È sempre quello che è corretto, non quello che ho ricordato male :-) ... anche se in questo caso mi sembra di aver sbagliato a ricordare!
Kerrek SB,

1
Ma come viene dichiarato g per il tipo generico T?
MK.

@MK. g è dichiarato come una funzione regolare con i parametri desiderati.
CoffeDeveloper

1
@cmdLP: hai ragione sul fatto che è ben definito per l'inoltro ripetuto, ma raramente è semanticamente corretto per il tuo programma. Portare membri di espressioni in avanti è tuttavia un caso utile. Aggiornerò la risposta.
Kerrek SB,

4

La risposta di Kerrek è molto utile, ma non risponde completamente alla domanda del titolo:

Quando usare std :: forward per inoltrare argomenti?

Per rispondere, dovremmo prima introdurre una nozione di riferimenti universali . Scott Meyers ha dato questo nome e oggi sono spesso chiamati riferimenti di inoltro. Fondamentalmente, quando vedi qualcosa del genere:

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

paramtieni presente che non è un riferimento di valore (come si potrebbe essere tentati di concludere), ma un riferimento universale *. I riferimenti universali sono caratterizzati da una forma molto limitata (solo T&&, senza const o qualificatori simili) e dalla deduzione del tipo: il tipo Tverrà dedotto quando fviene invocato. In breve, i riferimenti universali corrispondono ai riferimenti ai valori se sono inizializzati con valori e ai riferimenti ai valori se sono inizializzati con valori.

Ora è relativamente facile rispondere alla domanda originale - applicare std::forwarda:

  • un riferimento universale l'ultima volta che viene utilizzato nella funzione
  • un riferimento universale viene restituito da funzioni che restituiscono per valore

Un esempio per il primo caso:

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
}

Nel codice sopra, non vogliamo propavere un valore sconosciuto dopo che other.set(..)è terminato, quindi qui non viene eseguito alcun inoltro. Tuttavia, quando chiamiamo barinoltriamo propcome abbiamo finito con esso ebar possiamo fare tutto ciò che vuole con esso (ad esempio spostarlo).

Un esempio per il secondo caso:

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

Questo modello di funzione dovrebbe spostarsi propnel valore restituito se è un valore e copiarlo se è un valore. Nel caso in cui omettessimo std::forwardalla fine, creeremmo sempre una copia, che è più costosa quandoprop creeremmo sembra essere un valore.

* per essere pienamente precisi, un riferimento universale è un concetto di prendere un riferimento di valore a un parametro di modello non qualificato cv.


0

Questo esempio aiuta? Ho faticato a trovare un utile esempio non generico di std :: forward, ma ho trovato un esempio di un conto bancario che passiamo lungo il contante per essere depositato come argomento.

Pertanto, se disponiamo di una versione const di un account, dovremmo aspettarci che quando la passiamo al nostro modello di deposito <> venga chiamata la funzione const; e questo genera un'eccezione (l'idea era che fosse un account bloccato!)

Se disponiamo di un account non const, dovremmo essere in grado di modificare l'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;
    }
}

Costruire:

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

Uscita prevista:

# 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.