Quick reference of C++ value categories: Part 2




    Go to Part 1


    Passing parameters to a function


    When passing parameter to a function, category of a passed expression is implicitly converted to the category of function parameter: void f(TO_TYPE p); FROM_TYPE x; f(x); This implicit conversion takes place the same way as during an assignment (see "Assignment" section above) except that function definition cannot contain "auto" types.



    When function has multiple overloads with same number and types of parameters but different categories of parameters, overloading mechanism selects the most applicable function overload based on the category. In the table below function overloads are assigned an order in which each overload is selected when passing an expression of a particular category (e.g. 1 means that this overload will always be selected first if available):



    Footnotes:


    1 — a temporary created to hold a reference initializer persists until the end of function scope.


    Note:


    • If a function takes parameter by value, this does not mean that it always creates copy of object and cannot change source object. For example, if value is moved this function, move constructor will be called and source object will be changed.
    • You cannot have both T f() and const T f() function overloads
    • Function can be overloaded based on whether a parameter is a reference or not. But if both functions can take argument, ambiguity will need to be resolved manual (by casting to the relevant function pointer type).
    • Forwarding reference is most "greedy" — it wins as soon as there is no overload with exact match. That's why overloading a forwarding reference is usually not recommended (instead, avoid FR or avoid overloading or use tag dispatch or SFINAE). Constructor with one forwarding reference parameter should not be used, because it can conflict with copy and move constructors.
    • Forwarding references can make compiler errors more difficult to understand and fix, especially if they are used multiple times sequentially.
    • Forwarding reference does not work in the following situations:
      • You cannot forward {1, 2, 3} to a vector<int> with a forwarding reference.
      • You cannot forward 0 or NULL to a pointer with a forwarding reference (use nullptr instead).
      • You cannot forward integer static const and constexpr member variables with a forwarding reference, if they have no definitions.
      • You cannot forward overloaded function or template with a forwarding reference, because this creates ambiguity (can be disambiguated by specifying type).

    Examples and tests of variants with printing of each called constructor and operator:


    Examples and tests with printing of each called constructor and operator
    #include <iostream>
    #include <iomanip>
    #include <map>
    #include <vector>
    #include <string>
    
    using namespace std;
    
    template<class C, class T>
    auto contains(const C& v, const T& x)
    -> decltype(end(v), true)
    {
        return end(v) != std::find(begin(v), end(v), x);
    }
    
    template <class... Types>
    constexpr inline __attribute__((__always_inline__)) int UNUSED(Types&&...) {
        return 0;
    };
    
    map<string, map<string, string>> res;
    vector<string> froms;
    vector<string> tos;
    string from;
    string to;
    
    int ready = 0;
    
    void report(string st) {
        if (!from.empty() && !to.empty()) {
            res[from][to] += st;
        }
        if (ready) cout << st << " ";
    }
    
    struct T {
        T() {
            report("Dc");
        }
        T(int va) : a(va) {
            report("Pc");
        }
        T(const T& other) :
            a(other.a)
        {
            report("Cc");
        }
        T(T&& other) :
            a(std::exchange(other.a, 0))
        {
            report("Mc");
        }
        T& operator=(int va) {
            report("Va");
            a = va;
            return *this;
        }
        T& operator=(const T& rhs) {
            report("Ca");
            // check for self-assignment
            if(&rhs == this) return *this;
            a = rhs.a;
            return *this;
        }
        T& operator=(T&& rhs) {
            report("Ma");
            // check for self-assignment
            if(&rhs == this) return *this;
            a = std::exchange(rhs.a, 0);
            return *this;
        }
        ~T() {
            report("D");
        }
        int a = 1;
    };
    
    void func_start() {
        cout << "|";
    }
    
    T Fprv() { return T(4); }
    const T Fcprv() { return T(5); }
    T gs;
    
    void t(T s)             { func_start(); cout << s.a; }
    void ct(const T s)      { func_start(); cout << s.a; }
    void tr(T& s)           { func_start(); cout << s.a; }
    void ctr(const T& s)    { func_start(); cout << s.a; }
    void trr(T&& s)         { func_start(); cout << s.a; }
    void ctrr(const T&& s)  { func_start(); cout << s.a; }
    template<typename Z>
    void pf(Z&& s)          { func_start(); gs = forward<Z>(s); cout << gs.a; }
    
    void print_col(const string &st, int width) {
        cout << endl << left << setw(width) << st;
    }
    
    void test_pass(string lto, string lfrom) {
        from = lfrom;
        to = lto;
        res[from][to] = "";
        if (!from.empty() && !to.empty()) {
            if (!contains(froms, from)) froms.push_back(from);
            if (!contains(tos, to)) tos.push_back(to);
        }
        print_col(lto + "(" + lfrom + "): ", 20);
    }
    
    #define EVAL(x) #x
    
    #define TEST_PASS(t, v) { \
        test_pass(EVAL(t), #v); \
        t(v); \
        cout << "-"; \
    }
    
    void test_conversion() {
        ready = 1;
        T LV;
        const T CLV;
        T& LR = LV;
        const T& CLR = LV;
        //T&& XV = T(); -- actually LR
        //const T&& CXV = T(); -- actually LR
        auto &&fr = T();
    
    #undef DT
    #define DT t
        TEST_PASS(DT, 2);
        TEST_PASS(DT, Fprv());
        TEST_PASS(DT, Fcprv());
        TEST_PASS(DT, LV);
        TEST_PASS(DT, CLV);
        TEST_PASS(DT, LR);
        TEST_PASS(DT, CLR);
        TEST_PASS(DT, move(LV));
        TEST_PASS(DT, move(CLV));
        TEST_PASS(DT, fr);
    #undef DT
    #define DT ct
        TEST_PASS(DT, 2);
        TEST_PASS(DT, Fprv());
        TEST_PASS(DT, Fcprv());
        TEST_PASS(DT, LV);
        TEST_PASS(DT, CLV);
        TEST_PASS(DT, LR);
        TEST_PASS(DT, CLR);
        TEST_PASS(DT, move(LV));
        TEST_PASS(DT, move(CLV));
        TEST_PASS(DT, fr);
    #undef DT
    #define DT tr
        //TEST_PASS(DT, 2);
        //TEST_PASS(DT, Fprv());
        //TEST_PASS(DT, Fcprv());
        TEST_PASS(DT, LV);
        //TEST_PASS(DT, CLV);
        TEST_PASS(DT, LR);
        //TEST_PASS(DT, CLR);
        //TEST_PASS(DT, move(LV));
        //TEST_PASS(DT, move(CLV));
        TEST_PASS(DT, fr);
    #undef DT
    #define DT ctr
        TEST_PASS(DT, 2);
        TEST_PASS(DT, Fprv());
        TEST_PASS(DT, Fcprv());
        TEST_PASS(DT, LV);
        TEST_PASS(DT, CLV);
        TEST_PASS(DT, LR);
        TEST_PASS(DT, CLR);
        TEST_PASS(DT, move(LV));
        TEST_PASS(DT, move(CLV));
        TEST_PASS(DT, fr);
    #undef DT
    #define DT trr
        TEST_PASS(DT, 2);
        TEST_PASS(DT, Fprv());
        //TEST_PASS(DT, Fcprv());
        //TEST_PASS(DT, LV);
        //TEST_PASS(DT, CLV);
        //TEST_PASS(DT, LR);
        //TEST_PASS(DT, CLR);
        TEST_PASS(DT, move(LV));
        //TEST_PASS(DT, move(CLV));
        //TEST_PASS(DT, fr);
    #undef DT
    #define DT ctrr
        TEST_PASS(DT, 2);
        TEST_PASS(DT, Fprv());
        TEST_PASS(DT, Fcprv());
        //TEST_PASS(DT, LV);
        //TEST_PASS(DT, CLV);
        //TEST_PASS(DT, LR);
        //TEST_PASS(DT, CLR);
        TEST_PASS(DT, move(LV));
        TEST_PASS(DT, move(CLV));
        //TEST_PASS(DT, fr);
    #undef DT
    #define DT pf
        TEST_PASS(DT, 2);
        TEST_PASS(DT, Fprv());
        TEST_PASS(DT, Fcprv());
        TEST_PASS(DT, LV);
        TEST_PASS(DT, CLV);
        TEST_PASS(DT, LR);
        TEST_PASS(DT, CLR);
        TEST_PASS(DT, move(LV));
        TEST_PASS(DT, move(CLV));
        TEST_PASS(DT, fr);
    
        cout << endl;
        const int twidth = 10;
        cout << left << setw(twidth) << "From:";
        for (const auto& lto : tos) {
            cout << left << setw(twidth) << lto;
        }
        cout << endl;
        for (const auto& lfrom : froms) {
            cout << left << setw(twidth) << lfrom;
            for (const auto& lto : tos) {
                if (!res.count(lfrom) || !res[lfrom].count(lto)) {
                    cout << left << setw(twidth) << "-";
                } else if (res[lfrom][lto].empty()) {
                    cout << left << setw(twidth) << "+";
                } else {
                    cout << left << setw(twidth) << res[lfrom][lto];
                }
            }
            cout << endl;
        }
    
        cout << endl;
    }
    
    int main() {
        test_conversion();
        cout << endl;
        return 0;
    }
    
    /* Output:
    
    Dc Dc Dc
    t(2):               Pc |2D -
    t(Fprv()):          Pc |4D -
    t(Fcprv()):         Pc |5D -
    t(LV):              Cc |1D -
    t(CLV):             Cc |1D -
    t(LR):              Cc |1D -
    t(CLR):             Cc |1D -
    t(move(LV)):        Mc |1D -
    t(move(CLV)):       Cc |1D -
    t(fr):              Cc |1D -
    ct(2):              Pc |2D -
    ct(Fprv()):         Pc |4D -
    ct(Fcprv()):        Pc |5D -
    ct(LV):             Cc |0D -
    ct(CLV):            Cc |1D -
    ct(LR):             Cc |0D -
    ct(CLR):            Cc |0D -
    ct(move(LV)):       Mc |0D -
    ct(move(CLV)):      Cc |1D -
    ct(fr):             Cc |1D -
    tr(LV):             |0-
    tr(LR):             |0-
    tr(fr):             |1-
    ctr(2):             Pc |2D -
    ctr(Fprv()):        Pc |4D -
    ctr(Fcprv()):       Pc |5D -
    ctr(LV):            |0-
    ctr(CLV):           |1-
    ctr(LR):            |0-
    ctr(CLR):           |0-
    ctr(move(LV)):      |0-
    ctr(move(CLV)):     |1-
    ctr(fr):            |1-
    trr(2):             Pc |2D -
    trr(Fprv()):        Pc |4D -
    trr(move(LV)):      |0-
    ctrr(2):            Pc |2D -
    ctrr(Fprv()):       Pc |4D -
    ctrr(Fcprv()):      Pc |5D -
    ctrr(move(LV)):     |0-
    ctrr(move(CLV)):    |1-
    pf(2):              |Va 2-
    pf(Fprv()):         Pc |Ma 4D -
    pf(Fcprv()):        Pc |Ca 5D -
    pf(LV):             |Ca 0-
    pf(CLV):            |Ca 1-
    pf(LR):             |Ca 0-
    pf(CLR):            |Ca 0-
    pf(move(LV)):       |Ma 0-
    pf(move(CLV)):      |Ca 1-
    pf(fr):             |Ca 1-
    From:     t         ct        tr        ctr       trr       ctrr      pf
    2         PcD       PcD       -         PcD       PcD       PcD       Va
    Fprv()    PcD       PcD       -         PcD       PcD       PcD       PcMaD
    Fcprv()   PcD       PcD       -         PcD       -         PcD       PcCaD
    LV        CcD       CcD       +         +         -         -         Ca
    CLV       CcD       CcD       -         +         -         -         Ca
    LR        CcD       CcD       +         +         -         -         Ca
    CLR       CcD       CcD       -         +         -         -         Ca
    move(LV)  McD       McD       -         +         +         +         Ma
    move(CLV) CcD       CcD       -         +         -         +         Ca
    fr        CcD       CcD       +         +         -         -         Ca
    
    D D D
    
    */

    Links:


    Overload resolution
    Const reference vs move semantics
    Advantages of pass by value and std::move over pass by reference


    Returning from a function


    When returning an expression from a function, category of returned expression FROM_TYPE can mismatch category of function return type RETURN_TYPE, which can mismatch category of assigned variable TO_TYPE: RETURN_TYPE f() { FROM_TYPE x; return x; } TO_TYPE y = f();


    In this case implicit conversion can occur two times:


    • When converting from FROM_TYPE to RETURN TYPE. This conversion is the same as during the assignment, except trying to convert PRV or CPRV to T&&, const T&& or const T& (such conversions cannot occur, because lifetime of temporary object cannot be extended longer than function scope, link)
    • When converting from RETURN_TYPE to TO_TYPE — a usual assignment conversion (see "Assigning value categories" above).


    Footnotes:


    1 — a temporary created during assignment to hold a reference initializer persists until the end of its reference's scope (red font).


    Examples and tests of variants with printing of used copy/move constructors and operators:


    Examples and tests with printing of each called constructor and operator
    #include <iostream>
    #include <iomanip>
    #include <map>
    #include <vector>
    #include <string>
    
    using namespace std;
    
    template<class C, class T>
    auto contains(const C& v, const T& x)
    -> decltype(end(v), true)
    {
        return end(v) != std::find(begin(v), end(v), x);
    }
    
    template <class... Types>
    constexpr inline __attribute__((__always_inline__)) int UNUSED(Types&&...) {
        return 0;
    };
    
    map<string, map<string, string>> res;
    vector<string> froms;
    vector<string> tos;
    string from;
    string to;
    
    int ready = 0;
    
    void report(string st) {
        if (!from.empty() && !to.empty()) {
            res[from][to] += st;
        }
        if (ready) cout << st << " ";
    }
    
    struct T {
        T() {
            report("Dc");
        }
        T(int va) : a(va) {
            report("Pc");
        }
        T(const T& other) :
            a(other.a)
        {
            report("Cc");
        }
        T(T&& other) :
            a(std::exchange(other.a, 0))
        {
            report("Mc");
        }
        T& operator=(int va) {
            report("Va");
            a = va;
            return *this;
        }
        T& operator=(const T& rhs) {
            report("Ca");
            // check for self-assignment
            if(&rhs == this) return *this;
            a = rhs.a;
            return *this;
        }
        T& operator=(T&& rhs) {
            report("Ma");
            // check for self-assignment
            if(&rhs == this) return *this;
            a = std::exchange(rhs.a, 0);
            return *this;
        }
        ~T() {
            report("D");
        }
        int a = 1;
    };
    
    T lv;
    const T clv;
    T& lr = lv;
    T&& rr = std::move(lv);
    const T& clr = clv;
    const T&& crr = std::move(clv);
    auto&& arr = std::move(lv);
    
    T Fprv() { return T(); }
    const T Fcprv() { return T(); }
    
    void func_start() {
        cout << "|";
    }
    
    T prv_t() { func_start(); return T(2); }
    const T prv_ct() { func_start(); return T(2); }
    // Prohibited (returning reference to temporary object):
    // T& prv_tr() { func_start(); return T(2); }
    // const T& prv_ctr() { func_start(); return T(2); }
    // T&& prv_trr() { func_start(); return T(2); }
    // const T&& prv_ctrr() { func_start(); return T(2); }
    template<typename Z> Z&& prv_fr() { func_start(); return Z(2); }
    
    /* Same as prv_ (I tested)
    T cprv_t() { func_start(); return Fcprv(); }
    const T cprv_ct() { func_start(); return Fcprv(); }
    // Prohibited (returning reference to temporary object):
    // T& cprv_tr() { func_start(); return Fcprv(); }
    // const T& cprv_ctr() { func_start(); return Fcprv(); }
    // T&& cprv_trr() { func_start(); return Fcprv(); }
    // const T&& cprv_ctrr() { func_start(); return Fcprv(); }
    template<typename Z> Z&& cprv_fr() { func_start(); return Fcprv(); }
    */
    
    /* Same as prv_ (I tested)
    T lit_t() { func_start(); return 3; }
    const T lit_ct() { func_start(); return 3; }
    //T& lit_tr() { func_start(); return 3; }
    //const T& lit_ctr() { func_start(); return 3; }
    //T&& lit_trr() { func_start(); return 3; }
    //const T&& lit_ctrr() { func_start(); return 3; }
    template<typename Z> Z&& lit_fr() { func_start(); return 3; }
     */
    
    T lr_t()                { func_start(); return lr; }
    const T lr_ct()         { func_start(); return lr; }
    T& lr_tr()              { func_start(); return lr; }
    const T& lr_ctr()       { func_start(); return lr; }
    //T&& lr_trr()          { func_start(); return lr; }
    //const T&& lr_ctrr()   { func_start(); return lr; }
    template<typename Z> Z&& lr_fr() { func_start(); return lr; }
    
    T clr_t()                { func_start(); return clr; }
    const T clr_ct()         { func_start(); return clr; }
    //T& clr_tr()              { func_start(); return clr; }
    const T& clr_ctr()       { func_start(); return clr; }
    //T&& clr_trr()          { func_start(); return clr; }
    //const T&& clr_ctrr()   { func_start(); return clr; }
    template<typename Z> Z&& clr_fr() { func_start(); return clr; }
    
    /* This is the same as xv and cxv (I tested it)
    T rr_t()                { func_start(); return move(rr); }
    const T rr_ct()         { func_start(); return move(rr); }
    //T& rr_tr()              { func_start(); return move(rr); }
    const T& rr_ctr()       { func_start(); return move(rr); }
    T&& rr_trr()          { func_start(); return move(rr); }
    const T&& rr_ctrr()   { func_start(); return move(rr); }
    template<typename Z> Z&& rr_fr() { func_start(); return move(rr); }
    
    T crr_t()                { func_start(); return move(crr); }
    const T crr_ct()         { func_start(); return move(crr); }
    //T& crr_tr()              { func_start(); return move(crr); }
    const T& crr_ctr()       { func_start(); return move(crr); }
    //T&& crr_trr()          { func_start(); return move(crr); }
    const T&& crr_ctrr()   { func_start(); return move(crr); }
    template<typename Z> Z&& crr_fr() { func_start(); return move(crr); }
     */
    
    /* Same as lr_ (I tested)
    T arr_t()                { func_start(); return arr; }
    const T arr_ct()         { func_start(); return arr; }
    T& arr_tr()              { func_start(); return arr; }
    const T& arr_ctr()       { func_start(); return arr; }
    //T&& arr_trr()          { func_start(); return arr; }
    //const T&& arr_ctrr()   { func_start(); return arr; }
    template<typename Z> Z&& arr_fr() { func_start(); return arr; }
     */
    
    T lv_t()                { func_start(); return lv; }
    const T lv_ct()         { func_start(); return lv; }
    T& lv_tr()              { func_start(); return lv; }
    const T& lv_ctr()       { func_start(); return lv; }
    //T&& lv_trr()          { func_start(); return lv; }
    //const T&& lv_ctrr()   { func_start(); return lv; }
    template<typename Z> Z&& lv_fr() { func_start(); return lv; }
    
    T xv_t() { func_start(); return move(lv); }
    const T xv_ct() { func_start(); return move(lv); }
    //T& xv_tr() { func_start(); return move(lv); }
    const T& xv_ctr() { func_start(); return move(lv); }
    T&& xv_trr() { func_start(); return move(lv); }
    const T&& xv_ctrr() { func_start(); return move(lv); }
    template<typename Z> Z&& xv_fr() { func_start(); return move(lv); }
    
    T clv_t() { func_start(); return clv; }
    const T clv_ct() { func_start(); return clv; }
    //T& clv_tr() { func_start(); return clv; }
    const T& clv_ctr() { func_start(); return clv; }
    //T&& clv_trr() { func_start(); return clv; }
    //const T&& clv_ctrr() { func_start(); return clv; }
    template<typename Z> Z&& clv_fr() { func_start(); return clv; }
    
    T cxv_t() { func_start(); return move(clv); }
    const T cxv_ct() { func_start(); return move(clv); }
    //T& cxv_tr() { func_start(); return move(clv); }
    const T& cxv_ctr() { func_start(); return move(clv); }
    //T&& cxv_trr() { func_start(); return move(clv); }
    const T&& cxv_ctrr() { func_start(); return move(clv); }
    template<typename Z> Z&& cxv_fr() { func_start(); return move(clv); }
    
    void print_col(const string &st, int width) {
        cout << endl << left << setw(width) << st;
    }
    
    void test_call(string lto, string lfrom) {
        from = lfrom;
        to = lto;
        res[from][to] = "";
        if (!from.empty() && !to.empty()) {
            if (!contains(froms, from)) froms.push_back(from);
            if (!contains(tos, to)) tos.push_back(to);
        }
        print_col(lto + " = " + lfrom + ": ", 20);
    }
    
    #define EVAL(x) #x
    
    #define TEST_CALL(t, v) { \
        test_call(EVAL(t), #v); \
        t s = v(); \
        cout << s.a; \
        UNUSED(s); \
        cout << "-"; \
    }
    
    void test_return() {
        ready = 1;
        cout << endl;
    #define DT T
        TEST_CALL(DT, prv_t);
        TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        TEST_CALL(DT, lr_t);
        TEST_CALL(DT, lr_ct);
        TEST_CALL(DT, lr_tr);
        TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        TEST_CALL(DT, clr_t);
        TEST_CALL(DT, clr_ct);
        TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        TEST_CALL(DT, lv_t);
        TEST_CALL(DT, lv_ct);
        TEST_CALL(DT, lv_tr);
        TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        TEST_CALL(DT, xv_t);
        TEST_CALL(DT, xv_ct);
        TEST_CALL(DT, xv_ctr);
        TEST_CALL(DT, xv_trr);
        TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        TEST_CALL(DT, clv_t);
        TEST_CALL(DT, clv_ct);
        TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        TEST_CALL(DT, cxv_t);
        TEST_CALL(DT, cxv_ct);
        TEST_CALL(DT, cxv_ctr);
        TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT const T
        TEST_CALL(DT, prv_t);
        TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        TEST_CALL(DT, lr_t);
        TEST_CALL(DT, lr_ct);
        TEST_CALL(DT, lr_tr);
        TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        TEST_CALL(DT, clr_t);
        TEST_CALL(DT, clr_ct);
        TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        TEST_CALL(DT, lv_t);
        TEST_CALL(DT, lv_ct);
        TEST_CALL(DT, lv_tr);
        TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        TEST_CALL(DT, xv_t);
        TEST_CALL(DT, xv_ct);
        TEST_CALL(DT, xv_ctr);
        TEST_CALL(DT, xv_trr);
        TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        TEST_CALL(DT, clv_t);
        TEST_CALL(DT, clv_ct);
        TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        TEST_CALL(DT, cxv_t);
        TEST_CALL(DT, cxv_ct);
        TEST_CALL(DT, cxv_ctr);
        TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT T&
        //TEST_CALL(DT, prv_t);
        //TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        //TEST_CALL(DT, lr_t);
        //TEST_CALL(DT, lr_ct);
        TEST_CALL(DT, lr_tr);
        //TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        //TEST_CALL(DT, clr_t);
        //TEST_CALL(DT, clr_ct);
        //TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        //TEST_CALL(DT, lv_t);
        //TEST_CALL(DT, lv_ct);
        TEST_CALL(DT, lv_tr);
        //TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        //TEST_CALL(DT, xv_t);
        //TEST_CALL(DT, xv_ct);
        //TEST_CALL(DT, xv_ctr);
        //TEST_CALL(DT, xv_trr);
        //TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        //TEST_CALL(DT, clv_t);
        //TEST_CALL(DT, clv_ct);
        //TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        //TEST_CALL(DT, cxv_t);
        //TEST_CALL(DT, cxv_ct);
        //TEST_CALL(DT, cxv_ctr);
        //TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT T&&
        TEST_CALL(DT, prv_t);
        //TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        TEST_CALL(DT, lr_t);
        //TEST_CALL(DT, lr_ct);
        //TEST_CALL(DT, lr_tr);
        //TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        TEST_CALL(DT, clr_t);
        //TEST_CALL(DT, clr_ct);
        //TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        TEST_CALL(DT, lv_t);
        //TEST_CALL(DT, lv_ct);
        //TEST_CALL(DT, lv_tr);
        //TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        TEST_CALL(DT, xv_t);
        //TEST_CALL(DT, xv_ct);
        //TEST_CALL(DT, xv_ctr);
        TEST_CALL(DT, xv_trr);
        //TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        TEST_CALL(DT, clv_t);
        //TEST_CALL(DT, clv_ct);
        //TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        TEST_CALL(DT, cxv_t);
        //TEST_CALL(DT, cxv_ct);
        //TEST_CALL(DT, cxv_ctr);
        //TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT const T&&
        TEST_CALL(DT, prv_t);
        TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        TEST_CALL(DT, lr_t);
        TEST_CALL(DT, lr_ct);
        //TEST_CALL(DT, lr_tr);
        //TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        TEST_CALL(DT, clr_t);
        TEST_CALL(DT, clr_ct);
        //TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        TEST_CALL(DT, lv_t);
        TEST_CALL(DT, lv_ct);
        //TEST_CALL(DT, lv_tr);
        //TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        TEST_CALL(DT, xv_t);
        TEST_CALL(DT, xv_ct);
        //TEST_CALL(DT, xv_ctr);
        TEST_CALL(DT, xv_trr);
        TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        TEST_CALL(DT, clv_t);
        TEST_CALL(DT, clv_ct);
        //TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        TEST_CALL(DT, cxv_t);
        TEST_CALL(DT, cxv_ct);
        //TEST_CALL(DT, cxv_ctr);
        TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT const auto&&
        TEST_CALL(DT, prv_t);
        TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        TEST_CALL(DT, lr_t);
        TEST_CALL(DT, lr_ct);
        //TEST_CALL(DT, lr_tr);
        //TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        TEST_CALL(DT, clr_t);
        TEST_CALL(DT, clr_ct);
        //TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        TEST_CALL(DT, lv_t);
        TEST_CALL(DT, lv_ct);
        //TEST_CALL(DT, lv_tr);
        //TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        TEST_CALL(DT, xv_t);
        TEST_CALL(DT, xv_ct);
        //TEST_CALL(DT, xv_ctr);
        TEST_CALL(DT, xv_trr);
        TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        TEST_CALL(DT, clv_t);
        TEST_CALL(DT, clv_ct);
        //TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        TEST_CALL(DT, cxv_t);
        TEST_CALL(DT, cxv_ct);
        //TEST_CALL(DT, cxv_ctr);
        TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT auto&
        //TEST_CALL(DT, prv_t);
        TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        //TEST_CALL(DT, lr_t);
        TEST_CALL(DT, lr_ct);
        TEST_CALL(DT, lr_tr);
        TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        //TEST_CALL(DT, clr_t);
        TEST_CALL(DT, clr_ct);
        TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        //TEST_CALL(DT, lv_t);
        TEST_CALL(DT, lv_ct);
        TEST_CALL(DT, lv_tr);
        TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        //TEST_CALL(DT, xv_t);
        TEST_CALL(DT, xv_ct);
        TEST_CALL(DT, xv_ctr);
        //TEST_CALL(DT, xv_trr);
        TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        //TEST_CALL(DT, clv_t);
        TEST_CALL(DT, clv_ct);
        TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        //TEST_CALL(DT, cxv_t);
        TEST_CALL(DT, cxv_ct);
        TEST_CALL(DT, cxv_ctr);
        TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT const T&
        TEST_CALL(DT, prv_t);
        TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        TEST_CALL(DT, lr_t);
        TEST_CALL(DT, lr_ct);
        TEST_CALL(DT, lr_tr);
        TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        TEST_CALL(DT, clr_t);
        TEST_CALL(DT, clr_ct);
        TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        TEST_CALL(DT, lv_t);
        TEST_CALL(DT, lv_ct);
        TEST_CALL(DT, lv_tr);
        TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        TEST_CALL(DT, xv_t);
        TEST_CALL(DT, xv_ct);
        TEST_CALL(DT, xv_ctr);
        TEST_CALL(DT, xv_trr);
        TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        TEST_CALL(DT, clv_t);
        TEST_CALL(DT, clv_ct);
        TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        TEST_CALL(DT, cxv_t);
        TEST_CALL(DT, cxv_ct);
        TEST_CALL(DT, cxv_ctr);
        TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT const auto&
        TEST_CALL(DT, prv_t);
        TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        TEST_CALL(DT, lr_t);
        TEST_CALL(DT, lr_ct);
        TEST_CALL(DT, lr_tr);
        TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        TEST_CALL(DT, clr_t);
        TEST_CALL(DT, clr_ct);
        TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        TEST_CALL(DT, lv_t);
        TEST_CALL(DT, lv_ct);
        TEST_CALL(DT, lv_tr);
        TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        TEST_CALL(DT, xv_t);
        TEST_CALL(DT, xv_ct);
        TEST_CALL(DT, xv_ctr);
        TEST_CALL(DT, xv_trr);
        TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        TEST_CALL(DT, clv_t);
        TEST_CALL(DT, clv_ct);
        TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        TEST_CALL(DT, cxv_t);
        TEST_CALL(DT, cxv_ct);
        TEST_CALL(DT, cxv_ctr);
        TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    #undef DT
    #define DT auto&&
        TEST_CALL(DT, prv_t);
        TEST_CALL(DT, prv_ct);
        //TEST_CALL(DT, prv_fr);
        TEST_CALL(DT, lr_t);
        TEST_CALL(DT, lr_ct);
        TEST_CALL(DT, lr_tr);
        TEST_CALL(DT, lr_ctr);
        //TEST_CALL(DT, lr_fr);
        TEST_CALL(DT, clr_t);
        TEST_CALL(DT, clr_ct);
        TEST_CALL(DT, clr_ctr);
        //TEST_CALL(DT, clr_fr);
        TEST_CALL(DT, lv_t);
        TEST_CALL(DT, lv_ct);
        TEST_CALL(DT, lv_tr);
        TEST_CALL(DT, lv_ctr);
        //TEST_CALL(DT, lv_fr);
        TEST_CALL(DT, xv_t);
        TEST_CALL(DT, xv_ct);
        TEST_CALL(DT, xv_ctr);
        TEST_CALL(DT, xv_trr);
        TEST_CALL(DT, xv_ctrr);
        //TEST_CALL(DT, xv_fr);
        TEST_CALL(DT, clv_t);
        TEST_CALL(DT, clv_ct);
        TEST_CALL(DT, clv_ctr);
        //TEST_CALL(DT, clv_fr);
        TEST_CALL(DT, cxv_t);
        TEST_CALL(DT, cxv_ct);
        TEST_CALL(DT, cxv_ctr);
        TEST_CALL(DT, cxv_ctrr);
        //TEST_CALL(DT, cxv_fr);
    
        cout << endl;
        const int twidth = 9;
        cout << left << setw(twidth) << "From:";
        for (const auto& lto : tos) {
            cout << left << setw(twidth) << lto;
        }
        cout << endl;
        for (const auto& lfrom : froms) {
            cout << left << setw(twidth) << lfrom;
            for (const auto& lto : tos) {
                if (!res.count(lfrom) || !res[lfrom].count(lto)) {
                    cout << left << setw(twidth) << "-";
                } else if (res[lfrom][lto].empty()) {
                    cout << left << setw(twidth) << "+";
                } else {
                    cout << left << setw(twidth) << res[lfrom][lto];
                }
            }
            cout << endl;
        }
    }
    
    int main() {
    
        test_return();
    
        return 0;
    }
    
    /* Output:
    
    T = prv_t:          |Pc 2-D
    T = prv_ct:         |Pc 2-D
    T = lr_t:           |Cc 1-D
    T = lr_ct:          |Cc 1-D
    T = lr_tr:          |Cc 1-D
    T = lr_ctr:         |Cc 1-D
    T = clr_t:          |Cc 1-D
    T = clr_ct:         |Cc 1-D
    T = clr_ctr:        |Cc 1-D
    T = lv_t:           |Cc 1-D
    T = lv_ct:          |Cc 1-D
    T = lv_tr:          |Cc 1-D
    T = lv_ctr:         |Cc 1-D
    T = xv_t:           |Mc 1-D
    T = xv_ct:          |Mc 0-D
    T = xv_ctr:         |Cc 0-D
    T = xv_trr:         |Mc 0-D
    T = xv_ctrr:        |Cc 0-D
    T = clv_t:          |Cc 1-D
    T = clv_ct:         |Cc 1-D
    T = clv_ctr:        |Cc 1-D
    T = cxv_t:          |Cc 1-D
    T = cxv_ct:         |Cc 1-D
    T = cxv_ctr:        |Cc 1-D
    T = cxv_ctrr:       |Cc 1-D
    const T = prv_t:    |Pc 2-D
    const T = prv_ct:   |Pc 2-D
    const T = lr_t:     |Cc 0-D
    const T = lr_ct:    |Cc 0-D
    const T = lr_tr:    |Cc 0-D
    const T = lr_ctr:   |Cc 0-D
    const T = clr_t:    |Cc 1-D
    const T = clr_ct:   |Cc 1-D
    const T = clr_ctr:  |Cc 1-D
    const T = lv_t:     |Cc 0-D
    const T = lv_ct:    |Cc 0-D
    const T = lv_tr:    |Cc 0-D
    const T = lv_ctr:   |Cc 0-D
    const T = xv_t:     |Mc 0-D
    const T = xv_ct:    |Mc 0-D
    const T = xv_ctr:   |Cc 0-D
    const T = xv_trr:   |Mc 0-D
    const T = xv_ctrr:  |Cc 0-D
    const T = clv_t:    |Cc 1-D
    const T = clv_ct:   |Cc 1-D
    const T = clv_ctr:  |Cc 1-D
    const T = cxv_t:    |Cc 1-D
    const T = cxv_ct:   |Cc 1-D
    const T = cxv_ctr:  |Cc 1-D
    const T = cxv_ctrr: |Cc 1-D
    T& = lr_tr:         |0-
    T& = lv_tr:         |0-
    T&& = prv_t:        |Pc 2-D
    T&& = lr_t:         |Cc 0-D
    T&& = clr_t:        |Cc 1-D
    T&& = lv_t:         |Cc 0-D
    T&& = xv_t:         |Mc 0-D
    T&& = xv_trr:       |0-
    T&& = clv_t:        |Cc 1-D
    T&& = cxv_t:        |Cc 1-D
    const T&& = prv_t:  |Pc 2-D
    const T&& = prv_ct: |Pc 2-D
    const T&& = lr_t:   |Cc 0-D
    const T&& = lr_ct:  |Cc 0-D
    const T&& = clr_t:  |Cc 1-D
    const T&& = clr_ct: |Cc 1-D
    const T&& = lv_t:   |Cc 0-D
    const T&& = lv_ct:  |Cc 0-D
    const T&& = xv_t:   |Mc 0-D
    const T&& = xv_ct:  |Mc 0-D
    const T&& = xv_trr: |0-
    const T&& = xv_ctrr: |0-
    const T&& = clv_t:  |Cc 1-D
    const T&& = clv_ct: |Cc 1-D
    const T&& = cxv_t:  |Cc 1-D
    const T&& = cxv_ct: |Cc 1-D
    const T&& = cxv_ctrr: |1-
    const auto&& = prv_t: |Pc 2-D
    const auto&& = prv_ct: |Pc 2-D
    const auto&& = lr_t: |Cc 0-D
    const auto&& = lr_ct: |Cc 0-D
    const auto&& = clr_t: |Cc 1-D
    const auto&& = clr_ct: |Cc 1-D
    const auto&& = lv_t: |Cc 0-D
    const auto&& = lv_ct: |Cc 0-D
    const auto&& = xv_t: |Mc 0-D
    const auto&& = xv_ct: |Mc 0-D
    const auto&& = xv_trr: |0-
    const auto&& = xv_ctrr: |0-
    const auto&& = clv_t: |Cc 1-D
    const auto&& = clv_ct: |Cc 1-D
    const auto&& = cxv_t: |Cc 1-D
    const auto&& = cxv_ct: |Cc 1-D
    const auto&& = cxv_ctrr: |1-
    auto& = prv_ct:     |Pc 2-D
    auto& = lr_ct:      |Cc 0-D
    auto& = lr_tr:      |0-
    auto& = lr_ctr:     |0-
    auto& = clr_ct:     |Cc 1-D
    auto& = clr_ctr:    |1-
    auto& = lv_ct:      |Cc 0-D
    auto& = lv_tr:      |0-
    auto& = lv_ctr:     |0-
    auto& = xv_ct:      |Mc 0-D
    auto& = xv_ctr:     |0-
    auto& = xv_ctrr:    |0-
    auto& = clv_ct:     |Cc 1-D
    auto& = clv_ctr:    |1-
    auto& = cxv_ct:     |Cc 1-D
    auto& = cxv_ctr:    |1-
    auto& = cxv_ctrr:   |1-
    const T& = prv_t:   |Pc 2-D
    const T& = prv_ct:  |Pc 2-D
    const T& = lr_t:    |Cc 0-D
    const T& = lr_ct:   |Cc 0-D
    const T& = lr_tr:   |0-
    const T& = lr_ctr:  |0-
    const T& = clr_t:   |Cc 1-D
    const T& = clr_ct:  |Cc 1-D
    const T& = clr_ctr: |1-
    const T& = lv_t:    |Cc 0-D
    const T& = lv_ct:   |Cc 0-D
    const T& = lv_tr:   |0-
    const T& = lv_ctr:  |0-
    const T& = xv_t:    |Mc 0-D
    const T& = xv_ct:   |Mc 0-D
    const T& = xv_ctr:  |0-
    const T& = xv_trr:  |0-
    const T& = xv_ctrr: |0-
    const T& = clv_t:   |Cc 1-D
    const T& = clv_ct:  |Cc 1-D
    const T& = clv_ctr: |1-
    const T& = cxv_t:   |Cc 1-D
    const T& = cxv_ct:  |Cc 1-D
    const T& = cxv_ctr: |1-
    const T& = cxv_ctrr: |1-
    const auto& = prv_t: |Pc 2-D
    const auto& = prv_ct: |Pc 2-D
    const auto& = lr_t: |Cc 0-D
    const auto& = lr_ct: |Cc 0-D
    const auto& = lr_tr: |0-
    const auto& = lr_ctr: |0-
    const auto& = clr_t: |Cc 1-D
    const auto& = clr_ct: |Cc 1-D
    const auto& = clr_ctr: |1-
    const auto& = lv_t: |Cc 0-D
    const auto& = lv_ct: |Cc 0-D
    const auto& = lv_tr: |0-
    const auto& = lv_ctr: |0-
    const auto& = xv_t: |Mc 0-D
    const auto& = xv_ct: |Mc 0-D
    const auto& = xv_ctr: |0-
    const auto& = xv_trr: |0-
    const auto& = xv_ctrr: |0-
    const auto& = clv_t: |Cc 1-D
    const auto& = clv_ct: |Cc 1-D
    const auto& = clv_ctr: |1-
    const auto& = cxv_t: |Cc 1-D
    const auto& = cxv_ct: |Cc 1-D
    const auto& = cxv_ctr: |1-
    const auto& = cxv_ctrr: |1-
    auto&& = prv_t:     |Pc 2-D
    auto&& = prv_ct:    |Pc 2-D
    auto&& = lr_t:      |Cc 0-D
    auto&& = lr_ct:     |Cc 0-D
    auto&& = lr_tr:     |0-
    auto&& = lr_ctr:    |0-
    auto&& = clr_t:     |Cc 1-D
    auto&& = clr_ct:    |Cc 1-D
    auto&& = clr_ctr:   |1-
    auto&& = lv_t:      |Cc 0-D
    auto&& = lv_ct:     |Cc 0-D
    auto&& = lv_tr:     |0-
    auto&& = lv_ctr:    |0-
    auto&& = xv_t:      |Mc 0-D
    auto&& = xv_ct:     |Mc 0-D
    auto&& = xv_ctr:    |0-
    auto&& = xv_trr:    |0-
    auto&& = xv_ctrr:   |0-
    auto&& = clv_t:     |Cc 1-D
    auto&& = clv_ct:    |Cc 1-D
    auto&& = clv_ctr:   |1-
    auto&& = cxv_t:     |Cc 1-D
    auto&& = cxv_ct:    |Cc 1-D
    auto&& = cxv_ctr:   |1-
    auto&& = cxv_ctrr:  |1-
    From:    T        const T  T&       T&&      const T&&const auto&&auto&    const T& const auto&auto&&
    prv_t    PcD      PcD      -        PcD      PcD      PcD      -        PcD      PcD      PcD
    prv_ct   PcD      PcD      -        -        PcD      PcD      PcD      PcD      PcD      PcD
    lr_t     CcD      CcD      -        CcD      CcD      CcD      -        CcD      CcD      CcD
    lr_ct    CcD      CcD      -        -        CcD      CcD      CcD      CcD      CcD      CcD
    lr_tr    CcD      CcD      +        -        -        -        +        +        +        +
    lr_ctr   CcD      CcD      -        -        -        -        +        +        +        +
    clr_t    CcD      CcD      -        CcD      CcD      CcD      -        CcD      CcD      CcD
    clr_ct   CcD      CcD      -        -        CcD      CcD      CcD      CcD      CcD      CcD
    clr_ctr  CcD      CcD      -        -        -        -        +        +        +        +
    lv_t     CcD      CcD      -        CcD      CcD      CcD      -        CcD      CcD      CcD
    lv_ct    CcD      CcD      -        -        CcD      CcD      CcD      CcD      CcD      CcD
    lv_tr    CcD      CcD      +        -        -        -        +        +        +        +
    lv_ctr   CcD      CcD      -        -        -        -        +        +        +        +
    xv_t     McD      McD      -        McD      McD      McD      -        McD      McD      McD
    xv_ct    McD      McD      -        -        McD      McD      McD      McD      McD      McD
    xv_ctr   CcD      CcD      -        -        -        -        +        +        +        +
    xv_trr   McD      McD      -        +        +        +        -        +        +        +
    xv_ctrr  CcD      CcD      -        -        +        +        +        +        +        +
    clv_t    CcD      CcD      -        CcD      CcD      CcD      -        CcD      CcD      CcD
    clv_ct   CcD      CcD      -        -        CcD      CcD      CcD      CcD      CcD      CcD
    clv_ctr  CcD      CcD      -        -        -        -        +        +        +        +
    cxv_t    CcD      CcD      -        CcD      CcD      CcD      -        CcD      CcD      CcD
    cxv_ct   CcD      CcD      -        -        CcD      CcD      CcD      CcD      CcD      CcD
    cxv_ctr  CcD      CcD      -        -        -        -        +        +        +        +
    cxv_ctrr CcD      CcD      -        -        +        +        +        +        +        +
    
    */

    std::move or std::forward should not be applied to local objects when returning from function, if return value optimization (RVO) can be used to return a local object by value — because converting LV to XV will prevent compiler from applying RVO and moving object instead. When applied, RVO is more effective, because it avoids calling any additional copy/move constructors or operators.


    On the other hand, different compilers can not apply RVO in different situations, but in most situations compiler will be able to apply move assignment operator at least. If there is a risk that copy constructor will be called instead of RVO, you can use std::move or std::forward intead of returning object by value, but as far as I know the only situation when this can happen is when using ternary operator in return statement like return param ? a : b, which can be usually easily replaced with if statement. You can find more information about which compilers apply RVO in which situations https:// here and https:// here.


    Test of RVO with latest clang on Ubuntu (year 2019):


    Examples and tests with printing of each called constructor and operator
    #include <iostream>
    #include <string>
    #include <iomanip>
    
    using namespace std;
    
    void report(string st) {
        cout << st << " ";
    }
    
    struct T {
        T() {
            report("Dc");
        }
        T(int va) : a(va) {
            report("Pc");
        }
        T(const T& other) :
            a(other.a)
        {
            report("Cc");
        }
        T(T&& other) :
            a(std::exchange(other.a, 0))
        {
            report("Mc");
        }
        T& operator=(int va) {
            report("Va");
            a = va;
            return *this;
        }
        T& operator=(const T& rhs) {
            report("Ca");
            // check for self-assignment
            if(&rhs == this) return *this;
            a = rhs.a;
            return *this;
        }
        T& operator=(T&& rhs) {
            report("Ma");
            // check for self-assignment
            if(&rhs == this) return *this;
            a = std::exchange(rhs.a, 0);
            return *this;
        }
        ~T() {
            report("D");
        }
        int a = 1;
    };
    
    T urvo_single() {
      //const bool param = true;
      return T();
    }
    
    T urvo_two() {
      const bool param = true;
      if(param)
        return T();
      else
        return T();
    }
    
    T urvo_two_with_param(bool param) {
      if(param)
        return T();
      else
        return T();
    }
    
    T urvo_with_exception_1(bool param) {
      if(!param)
        throw std::exception();
    
      return T();
    }
    
    T urvo_with_exception_2(bool param) {
      if(param)
        return T();
      else
        throw std::exception();
    }
    
    T urvo_with_exception_3() {
      const bool param = true;
      if(param)
        return T();
      else
        throw std::exception();
    }
    
    static T make_X() { return T(); }
    
    T rrvo_single() {
      //const bool param = true;
      return make_X();
    }
    
    T rrvo_two() {
      const bool param = true;
      if(param)
        return make_X();
      else
        return make_X();
    }
    
    T rrvo_two_with_param(bool param) {
      if(param)
        return make_X();
      else
        return make_X();
    }
    
    T rrvo_with_exception_1(bool param) {
      if(!param)
        throw std::exception();
    
      return make_X();
    }
    
    T rrvo_with_exception_2(bool param) {
      if(param)
        return make_X();
      else
        throw std::exception();
    }
    
    T rrvo_with_exception_3() {
      const bool param = true;
      if(param)
        return make_X();
      else
        throw std::exception();
    }
    
    T nrvo_single_1() {
      T a;
      return a;
    }
    
    T nrvo_single_2() {
      {
        T a;
        return a;
      }
    }
    
    T nrvo_single_with_exception_1(bool param) {
      T a;
      if(!param)
        throw std::exception();
    
      return a;
    }
    
    T nrvo_single_with_exception_1a(bool param) {
      if(!param)
        throw std::exception();
      T a;
      return a;
    }
    
    T nrvo_single_with_exception_2(bool param) {
      T a;
      if(param)
        return a;
      else
        throw std::exception();
    
       // Silence compilation error, does not count as an additional
       // return statement as it is unreachable code
      return a;
    }
    
    T nrvo_single_with_exception_2a(bool param) {
      if(param) {
        T a;
        return a;
      } else
        throw std::exception();
    }
    
    T nrvo_single_with_exception_3() {
      const bool param = true;
      T a;
      if(param)
        return a;
      else
        throw std::exception();
    }
    
    T nrvo_single_with_exception_3a() {
      const bool param = true;
      if(param) {
        T a;
        return a;
      } else
        throw std::exception();
    }
    
    T nrvo_two_different_tern() {
      const bool param = true;
      T a, b;
      return param ? a : b;
    }
    
    T nrvo_two_different_if() {
      const bool param = true;
      T a, b;
      if(param)
        return a;
      else
        return b;
    }
    
    T nrvo_two_different_if_2() {
      const bool param = true;
      if(param) {
        T a;
        return a;
      } else {
        T b;
        return b;
      }
    }
    
    T nrvo_two_different_with_param_tern(bool param) {
      T a, b;
      return param ? a : b;
    }
    
    T nrvo_two_different_with_param_if(bool param) {
      T a, b;
      if(param)
        return a;
      else
        return b;
    }
    
    T nrvo_two_different_with_param_if_2(bool param) {
      if(param) {
        T a;
        return a;
      } else {
        T b;
        return b;
      }
    }
    
    T nrvo_two_equal_tern() {
      const bool param = true;
      T a;
      return param ? a : a;
    }
    
    T nrvo_two_equal_if() {
      const bool param = true;
      T a;
      if(param)
        return a;
      else
        return a;
    }
    
    T nrvo_two_equal_with_param_tern(bool param) {
      T a;
      return param ? a : a;
    }
    
    T nrvo_two_equal_with_param_if(bool param) {
      T a;
      if(param)
        return a;
      else
        return a;
    }
    
    T nrvo_urvo_mixed_static() {
        static const bool param = true;
        if (param)
            return T();
        T a;
        return a;
    }
    
    T nrvo_urvo_mixed_dynamic(bool param) {
        if (param)
            return T();
        T a;
        return a;
    }
    
    void print_col(const string &st, int width) {
        cout << endl << left << setw(width) << st;
    }
    
    #define CHECK_COPIES(stmt) {                         \
        print_col(#stmt ": ", 20);       \
        try {                                            \
            stmt;                                        \
        }                                                \
        catch(...) {                                     \
        }                                                \
    }                                                    \
    
    int main() {
        CHECK_COPIES( T a = urvo_single());
        CHECK_COPIES( T a = urvo_two());
        CHECK_COPIES( T a = urvo_two_with_param(true));
        CHECK_COPIES( T a = urvo_with_exception_1(true));
        CHECK_COPIES( T a = urvo_with_exception_2(true));
        CHECK_COPIES( T a = urvo_with_exception_3());
    
        cerr << " ";
    
        CHECK_COPIES( T a = rrvo_single());
        CHECK_COPIES( T a = rrvo_two());
        CHECK_COPIES( T a = rrvo_two_with_param(true));
        CHECK_COPIES( T a = rrvo_with_exception_1(true));
        CHECK_COPIES( T a = rrvo_with_exception_2(true));
        CHECK_COPIES( T a = rrvo_with_exception_3());
    
        cerr << " ";
    
        CHECK_COPIES( T a = nrvo_single_1());
        CHECK_COPIES( T a = nrvo_single_2());
        CHECK_COPIES( T a = nrvo_single_with_exception_1(true));
        CHECK_COPIES( T a = nrvo_single_with_exception_1a(true));
        CHECK_COPIES( T a = nrvo_single_with_exception_2(true));
        CHECK_COPIES( T a = nrvo_single_with_exception_2a(true));
        CHECK_COPIES( T a = nrvo_single_with_exception_3());
        CHECK_COPIES( T a = nrvo_single_with_exception_3a());
    
        cerr << " ";
    
        CHECK_COPIES( T a = nrvo_two_different_tern());
        CHECK_COPIES( T a = nrvo_two_different_if());
        CHECK_COPIES( T a = nrvo_two_different_if_2());
        CHECK_COPIES( T a = nrvo_two_different_with_param_tern(true));
        CHECK_COPIES( T a = nrvo_two_different_with_param_if(true));
        CHECK_COPIES( T a = nrvo_two_different_with_param_if_2(true));
        CHECK_COPIES( T a = nrvo_two_equal_tern());
        CHECK_COPIES( T a = nrvo_two_equal_if());
        CHECK_COPIES( T a = nrvo_two_equal_with_param_tern(true));
        CHECK_COPIES( T a = nrvo_two_equal_with_param_if(true));
    
        cerr << " ";
    
        CHECK_COPIES( T a = nrvo_urvo_mixed_static());
        CHECK_COPIES( T a = nrvo_urvo_mixed_dynamic(true));
    }
    
    /* Output:
    
     T a = urvo_single(): Dc D
     T a = urvo_two():   Dc D
     T a = urvo_two_with_param(true): Dc D
     T a = urvo_with_exception_1(true): Dc D
     T a = urvo_with_exception_2(true): Dc D
     T a = urvo_with_exception_3(): Dc D
     T a = rrvo_single(): Dc D
     T a = rrvo_two():   Dc D
     T a = rrvo_two_with_param(true): Dc D
     T a = rrvo_with_exception_1(true): Dc D
     T a = rrvo_with_exception_2(true): Dc D
     T a = rrvo_with_exception_3(): Dc D
     T a = nrvo_single_1(): Dc D
     T a = nrvo_single_2(): Dc D
     T a = nrvo_single_with_exception_1(true): Dc D
     T a = nrvo_single_with_exception_1a(true): Dc D
     T a = nrvo_single_with_exception_2(true): Dc D
     T a = nrvo_single_with_exception_2a(true): Dc D
     T a = nrvo_single_with_exception_3(): Dc D
     T a = nrvo_single_with_exception_3a(): Dc D
     T a = nrvo_two_different_tern(): Dc Dc Cc D D D
     T a = nrvo_two_different_if(): Dc Dc Mc D D D
     T a = nrvo_two_different_if_2(): Dc D
     T a = nrvo_two_different_with_param_tern(true): Dc Dc Cc D D D
     T a = nrvo_two_different_with_param_if(true): Dc Dc Mc D D D
     T a = nrvo_two_different_with_param_if_2(true): Dc D
     T a = nrvo_two_equal_tern(): Dc Cc D D
     T a = nrvo_two_equal_if(): Dc D
     T a = nrvo_two_equal_with_param_tern(true): Dc Cc D D
     T a = nrvo_two_equal_with_param_if(true): Dc D
     T a = nrvo_urvo_mixed_static(): Dc D
     T a = nrvo_urvo_mixed_dynamic(true): Dc D
    
    */

    Links:


    Copy elision
    Copy elision revisited
    Compare clang and gcc copy elision (tests with similar results are commented out)


    Passing and returning trivially copyable objects of small size


    Trivially copyable struct or class:


    • has no virtual members or virtual base classes
    • has default copy and move constructors and operators

    Trivially copyable objects of small size (up to 16 bytes) can be passed in CPU registers when passed to function by value — like scalar types.


    Also, when passing it by value, compiler has additional guarantee that object will not change during the course of the function even if other functions are called — this is why compiler can generate more effective code in this situations. If passed by const T&, there is no such guarantee, because object passed by constant reference can still be changed from other function, or multiple passed objects can use shared memory.


    For the same reason, std::string_view should be passed by value.


    Links:


    Trivially copyable


    Returning from a function with a reference parameter


    A reference to an object can be passed to a function so that function can change source object:


    T& change(T& X) { 
      X.change();
      return X; 
    }

    This method should not be used if temporary object can be created inside function and returned by value, because returning by value can take advantage of return value optimization (see above).


    Passing multiple parameters and returning one of them from function


    If all passed parameters are lvalues, you can pass and return by const T&. This gives high performance, because no copy/move constructors or operators are called:


    const T& get(const T& a, const T& b) {
      if ( /* something */ ) return a;
      else return b;
    }

    prvalue cannot be passed to get function, because this would result in returning a reference to a temporary object. If prvalue has to be passed,


    If both parameters are temporary objects (prvalues), T&& can be returned, which also gives high performance (no copy/move constructors or operators are called):


    T&& get(T&& a, T&& b) {
      if ( /* something */ ) return std::move(a);
      else return std::move(b);
    }

    If some of parameters are temporary objects (prvalues) and other are lvalues, it is difficult to high performance without complicating the function interface. In the following example if lvalue and prvalue are passed as A and B arguments into the function, copy constructor will be called if A is chosen and move constructor if B is chosen:


    T get(const T& a, T&& b) {
      if ( /* something */ ) return a;
      else return std::move(b);
    }

    Passing to constructor or setter


    In this section I describe passing an object of type T to an object of type C during construction of type C or by calling a setter:


    class C { 
      C(T X) : x(X) {} 
      void set(T X) { 
        x = X; 
      }
      T x;
    };
    
    T y;
    C c(y);
    c.set(y);

    • For copying an lvalue to x member variable in an object of class C:
      • with class C constructor: any of the following variants is good ("T move" variant is a bit less efficient due to calling an additional move constructor)
      • with class C set(T):
      • if T is copy-on-write (like CString in MFC/ATL) and x will not change later, any of the following variants is good ("T move" variant is a bit less efficient due to calling an additional move constructor)
      • if T is not copy-on write or x can change later, "T move" variant becomes inefficient, because it does not allow to avoid deallocation, even if already allocated memory in existing object x is enough for new object and class can reuse allocated memory (like std::string)
    • For moving an xvalue or prvalue to x member variable in an object of class C with C's constructor or C's set member function any of the following variants is good except "const T&" (because it requires copying). Perfect forwarding has a slight performance advantage due to avoiding calling move constructor when converting from different type, instead forwarding parameter of a different type to a parametrized constructor

    Variants:


    • const T& — optimal if we do not need to move objects or store them in dynamically growing containers
      class C { 
      C(const T& X) : x(X) {} 
      void set(const T& X) { 
      x = X; 
      }
      T x;
      };
    • const T& + T&& move — optimized variant if we need to support both lvalue copy and rvalue move. Can lead to code duplication, especially if constructors with different combinations of & and && are needed. Provides simple implementation of noexcept for effective use with containers.
      class C { 
      C(const T& X) : x(X) {} 
      C(T&& X) : x(std::move(X)) {} 
      void set(const T& X) { 
      x = X; 
      }
      void set(T&& X) { 
      x = std::move(X); 
      }
      T x;
      };
    • T move — looks simple, but not only intuitive (can avoid copying and change object, passed with std::move as xvalue). Less efficient than perfect forwarding and &+&& in some situations (does not allow to avoid deallocation, even if already allocated memory in existing object x is enough for new object and class can reuse allocated memory, calls excessive moves and destructors)
      class C { 
      C(T X) : x(std::move(X)) {} 
      void set(T X) { 
      x = std::move(X); 
      }
      T x;
      };
    • Perfect forwarding (PF) — most effective variant (especially when forwarding literals or other types, which can be converted to class with parametrized constructors), but it is more difficult to write, which increases risk of mistakes. Does not support virtual functions and has to be implemented in header file.
      class C { 
      template<typename Z>
      C(Z&& X) : x(std::forward<Z>(X)) {} 
      template<typename Z>
      void set(Z&& X) { 
      x = std::forward<Z>(X); 
      }
      T x;
      };
    • Strict perfect forwarding (PF Strict). Has same qualities as perfect forwarding, but is more safe, because it does not allow to convert types and thus cannot receive literals (actually, it is the only variant among discussed in this section, which does not allow to convert types).
      class C { 
      template<class Z, class=std::enable_if_t<std::is_same<std::decay_t<Z>, T>::value>>
      C(Z&& X) : x(std::forward<Z>(X)) {} 
      template<class Z, class=std::enable_if_t<std::is_same<std::decay_t<Z>, T>::value>>
      void set(Z&& X) { 
      x = std::forward<Z>(X); 
      }
      T x;
      };


    In the comparison tables below constructor properties are highlighted with green, setter member function properties are highlighted with yellow.



    Annotations:
    1 — Becomes combinatorial if constructor has multiple parameters
    2 — Function is technically noexcept, but copy before function call can result in exception
    3 — Requires noexcept(std::is_nothrow_assignable<Type&, T>::value)
    4 — Does not accept conversion
    5 — Unconditional deallocation leads to inability to reuse allocated storage (std::string). Not a problem for copy-on-write implementations (Tstring)
    6 — If you do not have overloaded assignment operator for value type in object class, this will be: VcMaD


    Examples and tests of variants with printing of used copy/move constructors and operators:


    Examples and tests with printing of each called constructor and operator
    #include <iostream>
    
    using namespace std;
    
    struct TStruct {
        TStruct() {
            cout << "Dc";
        }
        TStruct(int va) : a(va) {
            cout << "Vc";
        }
        TStruct(const TStruct& other) :
            a(other.a)
        {
            cout << "Cc";
        }
        TStruct(TStruct&& other) :
            a(std::exchange(other.a, 0))
        {
            cout << "Mc";
        }
        TStruct& operator=(int va) {
            cout << "Va";
            a = va;
            return *this;
        }
        TStruct& operator=(const TStruct& rhs) {
            cout << "Ca";
            // check for self-assignment
            if(&rhs == this) return *this;
            a = rhs.a;
            return *this;
        }
        TStruct& operator=(TStruct&& rhs) {
            cout << "Ma";
            // check for self-assignment
            if(&rhs == this) return *this;
            a = std::exchange(rhs.a, 0);
            return *this;
        }
        ~TStruct() {
            cout << "D";
        }
        int a = 1;
    };
    
    struct TRef {
        TRef(TStruct& tsv) : ts(tsv) {}
        void set(TStruct& tsv) { ts = tsv; }
        TStruct ts;
    };
    
    struct TConstRef {
        TConstRef(const TStruct& tsv) : ts(tsv) {}
        void set(const TStruct& tsv) { ts = tsv; }
        TStruct ts;
    };
    
    struct TConstRefAndRvalueRef {
        TConstRefAndRvalueRef(const TStruct& tsv) : ts(tsv) {
            cout << "Lr";
        }
        TConstRefAndRvalueRef(TStruct&& tsv) : ts(std::move(tsv)) {
            cout << "Rr";
        }
        void set(const TStruct& tsv) {
            cout << "Sl";
            ts = tsv;
        }
        void set(TStruct&& tsv) {
            cout << "Sr";
            ts = std::move(tsv);
        }
        TStruct ts;
    };
    
    struct TConstRvalueRef {
        TConstRvalueRef(const TStruct&& tsv) : ts(std::move(tsv)) {
        }
        void set(const TStruct&& tsv) {
            ts = std::move(tsv);
        }
        TStruct ts;
    };
    
    struct TValueMove {
        TValueMove(TStruct tsv) : ts(std::move(tsv)) {}
        void set(TStruct tsv) { ts = std::move(tsv); }
        TStruct ts;
    };
    
    struct TConstValueMove {
        TConstValueMove(const TStruct tsv) : ts(std::move(tsv)) {}
        void set(const TStruct tsv) { ts = std::move(tsv); }
        TStruct ts;
    };
    
    struct TPerfectForward {
        template<class T>
        TPerfectForward(T&& tsv) : ts(std::forward<T>(tsv)) {}
        template<class T>
        void set(T&& tsv) { ts = std::forward<T>(tsv); }
        TStruct ts;
    };
    
    struct TConstPerfectForward {
        template<class T>
        TConstPerfectForward(const T&& tsv) : ts(std::forward<T>(tsv)) {}
        template<class T>
        void set(const T&& tsv) { ts = std::forward<T>(tsv); }
        TStruct ts;
    };
    
    struct TPerfectForwardStrict {
        template<class T, class=std::enable_if_t<std::is_same<std::decay_t<T>, TStruct>::value>>
        TPerfectForwardStrict(T&& tsv) : ts(std::forward<T>(tsv)) {}
        template<class T, class=std::enable_if_t<std::is_same<std::decay_t<T>, TStruct>::value>>
        void set(T&& tsv) { ts = std::forward<T>(tsv); }
        TStruct ts;
    };
    
    struct TMultiVariant {
        TMultiVariant(const TStruct& tsv) : ts(tsv) {
            cout << "Lr";
        }
        TMultiVariant(TStruct&& tsv) : ts(std::move(tsv)) {
            cout << "Rr";
        }
        void set(const TStruct& tsv) {
            cout << "Sl";
            ts = tsv;
        }
        void set(TStruct&& tsv) {
            cout << "Sr";
            ts = std::move(tsv);
        }
    
        template<class T>
        TMultiVariant(T&& tsv) : ts(std::forward<T>(tsv)) {
            cout << "Pf";
        }
        template<class T>
        void set(T&& tsv) {
            cout << "Sp";
            ts = std::forward<T>(tsv);
        }
        TStruct ts;
    };
    
    struct TMultiVariantStrict {
        TMultiVariantStrict(const TStruct& tsv) : ts(tsv) {
            cout << "Lr";
        }
        TMultiVariantStrict(TStruct&& tsv) : ts(std::move(tsv)) {
            cout << "Rr";
        }
        void set(const TStruct& tsv) {
            cout << "Sl";
            ts = tsv;
        }
        void set(TStruct&& tsv) {
            cout << "Sr";
            ts = std::move(tsv);
        }
    
        template<class T, class=std::enable_if_t<std::is_same<std::decay_t<T>, TStruct>::value>>
        TMultiVariantStrict(T&& tsv) : ts(std::forward<T>(tsv)) {
            cout << "Pf";
        }
        template<class T, class=std::enable_if_t<std::is_same<std::decay_t<T>, TStruct>::value>>
        void set(T&& tsv) {
            cout << "Sp";
            ts = std::forward<T>(tsv);
        }
        TStruct ts;
    };
    
    TStruct get_st() {
        return TStruct(7);
    }
    
    void detect_change(int& param, int val) {
        if (param != val) {
            cout << "!";
            param = val;
        } else {
            cout << "-";
        }
    }
    
    void test_passing() {
        cout << "TStruct st(100): ";
        TStruct st(100);
    
        // Test constructing with object
        cout << endl << "TRef (st): ";
        TRef rf(st);
        detect_change(st.a, 100);
        cout << endl << "TConstRef (st):                ";
        TConstRef crf(st);
        detect_change(st.a, 100);
        cout << endl << "TConstRefAndRvalueRef (st):    ";
        TConstRefAndRvalueRef crf_rvf(st);
        detect_change(st.a, 100);
        // Will not compile
        // cout << endl << "TConstRvalueRef (st): ";
        // TConstRvalueRef crvf(st);
        // detect_change(st.a, 100);
        cout << endl << "TValueMove (st):               ";
        TValueMove vm(st);
        detect_change(st.a, 100);
        cout << endl << "TConstValueMove (st):          ";
        TConstValueMove cvm(st);
        detect_change(st.a, 100);
        cout << endl << "TPerfectForward (st):          ";
        TPerfectForward pf(st);
        detect_change(st.a, 100);
        // Will not compile
        // cout << endl << "TConstPerfectForward (st): ";
        // TConstPerfectForward cpf(st);
        // detect_change(st.a, 100);
        cout << endl << "TPerfectForwardStrict (st):    ";
        TPerfectForwardStrict pft(st);
        detect_change(st.a, 100);
        cout << endl << "TMultiVariant (st):            ";
        TMultiVariant mv(st);
        detect_change(st.a, 100);
        cout << endl << "TMultiVariantStrict (st):      ";
        TMultiVariantStrict mvt(st);
        detect_change(st.a, 100);
    
        // Test constructing with moved object
        // Will not compile because of std::move
        // cout << endl << "TRef (std::move(st)): ";
        // TRef rf4(std::move(st));
        // detect_change(st.a, 100);
        cout << endl << "TConstRef (std::move(st)):             ";
        TConstRef crf4(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TConstRefAndRvalueRef (std::move(st)): ";
        TConstRefAndRvalueRef crf_rvf4(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TConstRvalueRef (std::move(st)):       ";
        TConstRvalueRef crvf4(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TValueMove (std::move(st)):            ";
        TValueMove vm4(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TConstValueMove (std::move(st)):       ";
        TConstValueMove cvm4(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TPerfectForward (std::move(st)):       ";
        TPerfectForward pf4(std::move(st));
        detect_change(st.a, 100);
        // Will not compile
        // cout << endl << "TConstPerfectForward (std::move(st)): ";
        // TConstPerfectForward cpf4(std::move(st));
        // detect_change(st.a, 100);
        cout << endl << "TPerfectForwardStrict (std::move(st)): ";
        TPerfectForwardStrict pft4(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TMultiVariant (std::move(st)):         ";
        TMultiVariant mv4(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TMultiVariantStrict (std::move(st)):   ";
        TMultiVariantStrict mvt4(std::move(st));
        detect_change(st.a, 100);
    
        // Test constructing with temporary object
        // Will not compile
        // cout << endl << "TRef (TStruct(6)): ";
        // TRef rf3(TStruct(6));
         cout << endl << "TConstRef (TStruct(6)):               ";
        TConstRef crf3(TStruct(6));
        cout << endl << "TConstRefAndRvalueRef (TStruct(6)):    ";
        TConstRefAndRvalueRef crf_rvf3(TStruct(6));
        cout << endl << "TConstRvalueRef (TStruct(6)):          ";
        TConstRvalueRef crvf3(TStruct(6));
        cout << endl << "TValueMove (TStruct(6)):               ";
        TValueMove vm3(TStruct(6));
        cout << endl << "TConstValueMove (TStruct(6)):          ";
        TConstValueMove cvm3(TStruct(6));
        cout << endl << "TPerfectForward (TStruct(6)):          ";
        TPerfectForward pf3(TStruct(6));
        // Will not compile
        //cout << endl << "TConstPerfectForward (TStruct(6)): ";
        //TConstPerfectForward cpf3(TStruct(6));
        cout << endl << "TPerfectForwardStrict (TStruct(6)):    ";
        TPerfectForwardStrict pft3(TStruct(6));
        cout << endl << "TMultiVariant (TStruct(6)):            ";
        TMultiVariant mv3(TStruct(6));
        cout << endl << "TMultiVariantStrict (TStruct(6)):      ";
        TMultiVariantStrict mvt3(TStruct(6));
    
        // Test constructing with RVO object
        // Will not compile
        //cout << endl << "TRef (get_st()): ";
        //TRef rf5(get_st());
        cout << endl << "TConstRef (get_st()):               ";
        TConstRef crf5(get_st());
        cout << endl << "TConstRefAndRvalueRef (get_st()):   ";
        TConstRefAndRvalueRef crf_rvf5(get_st());
        cout << endl << "TConstRvalueRef (get_st()):         ";
        TConstRvalueRef crvf5(get_st());
        cout << endl << "TValueMove (get_st()):              ";
        TValueMove vm5(get_st());
        cout << endl << "TConstValueMove (get_st()):         ";
        TConstValueMove cvm5(get_st());
        cout << endl << "TPerfectForward (get_st()):         ";
        TPerfectForward pf5(get_st());
        //cout << endl << "TConstPerfectForward (get_st()): ";
        //TConstPerfectForward cpf5(get_st());
        cout << endl << "TPerfectForwardStrict (get_st()):   ";
        TPerfectForwardStrict pft5(get_st());
        cout << endl << "TMultiVariant (get_st()):           ";
        TMultiVariant mv5(get_st());
        cout << endl << "TMultiVariantStrict (get_st()):     ";
        TMultiVariantStrict mvt5(get_st());
    
        // Test constructing with literal
        // Will not compile
        //cout << endl << "TRef (5): ";
        //TRef rf2(5);
        cout << endl << "TConstRef (5):                     ";
        TConstRef crf2(5);
        cout << endl << "TConstRefAndRvalueRef (5):         ";
        TConstRefAndRvalueRef crf_rvf2(5);
        cout << endl << "TConstRvalueRef (5):               ";
        TConstRvalueRef crvf2(5);
        cout << endl << "TValueMove (5):                    ";
        TValueMove vm2(5);
        cout << endl << "TConstValueMove (5):               ";
        TConstValueMove cvm2(5);
        cout << endl << "TPerfectForward (5):               ";
        TPerfectForward pf2(5);
        // Will not compile
        //cout << endl << "TConstPerfectForward (5): ";
        //TConstPerfectForward cpf2(5);
        // Will not compile due to SFINAE test
        //cout << endl << "TPerfectForwardStrict (5): ";
        //TPerfectForwardStrict pft2(5);
        cout << endl << "TMultiVariant (5):                 ";
        TMultiVariant mv2(5);
        cout << endl << "TMultiVariantStrict (5):           ";
        TMultiVariantStrict mvt2(5);
    
        // Test setting with object
        st.a = 100;
        cout << endl << "TRef set(st):                      ";
        rf.set(st);
        detect_change(st.a, 100);
        cout << endl << "TConstRef set(st):                 ";
        crf.set(st);
        detect_change(st.a, 100);
        cout << endl << "TConstRefAndRvalueRef set(st):     ";
        crf_rvf.set(st);
        detect_change(st.a, 100);
        // Will not compile
        // cout << endl << "TConstRvalueRef set(st): ";
        // crvf2.set(st);
        // detect_change(st.a, 100);
        cout << endl << "TValueMove set(st):                ";
        vm.set(st);
        detect_change(st.a, 100);
        cout << endl << "TConstValueMove set(st):           ";
        cvm.set(st);
        detect_change(st.a, 100);
        cout << endl << "TPerfectForward set(st):           ";
        pf.set(st);
        detect_change(st.a, 100);
        cout << endl << "TPerfectForwardStrict set(st):     ";
        pft.set(st);
        detect_change(st.a, 100);
        cout << endl << "TMultiVariant set(st):             ";
        mv.set(st);
        detect_change(st.a, 100);
        cout << endl << "TMultiVariantStrict set(st):       ";
        mvt.set(st);
        detect_change(st.a, 100);
    
        // Test setting with moved object
        // Will not compile
        //cout << endl << "TRef set(std::move(st)): ";
        //rf.set(std::move(st));
        //detect_change(st.a, 100);
        cout << endl << "TConstRef set(std::move(st)):              ";
        crf.set(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TConstRefAndRvalueRef set(std::move(st)):  ";
        crf_rvf.set(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TConstRvalueRef set(std::move(st)):        ";
        crvf2.set(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TValueMove set(std::move(st)):             ";
        vm.set(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TConstValueMove set(std::move(st)):        ";
        cvm.set(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TPerfectForward set(std::move(st)):        ";
        pf.set(std::move(st));
        detect_change(st.a, 100);
        detect_change(st.a, 100);
        cout << endl << "TPerfectForwardStrict set(std::move(st)):  ";
        pft.set(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TMultiVariant set(std::move(st)):          ";
        mv.set(std::move(st));
        detect_change(st.a, 100);
        cout << endl << "TMultiVariantStrict set(std::move(st)):    ";
        mvt.set(std::move(st));
        detect_change(st.a, 100);
    
        // Test setting with constructed temporary object
        // Will not compile
        //cout << endl << "TRef set(TStruct(8)): ";
        //rf.set(TStruct(8));
        cout << endl << "TConstRef set(TStruct(8)):             ";
        crf.set(TStruct(8));
        cout << endl << "TConstRefAndRvalueRef set(TStruct(8)): ";
        crf_rvf.set(TStruct(8));
        cout << endl << "TConstRvalueRef set(TStruct(8)):       ";
        crvf2.set(TStruct(8));
        cout << endl << "TValueMove set(TStruct(8)):            ";
        vm.set(TStruct(8));
        cout << endl << "TConstValueMove set(TStruct(8)):       ";
        cvm.set(TStruct(8));
        cout << endl << "TPerfectForward set(TStruct(8)):       ";
        pf.set(TStruct(8));
        cout << endl << "TPerfectForwardStrict set(TStruct(8)): ";
        pft.set(TStruct(8));
        cout << endl << "TMultiVariant set(TStruct(8)):         ";
        mv.set(TStruct(8));
        cout << endl << "TMultiVariantStrict set(TStruct(8)):   ";
        mvt.set(TStruct(8));
    
        // Test setting with RVO object
        // Will not compile
        //cout << endl << "TRef set(get_st()): ";
        //rf.set(get_st());
        cout << endl << "TConstRef set(get_st()):              ";
        crf.set(get_st());
        cout << endl << "TConstRefAndRvalueRef set(get_st()):  ";
        crf_rvf.set(get_st());
        cout << endl << "TConstRvalueRef set(get_st()):        ";
        crvf2.set(get_st());
        cout << endl << "TValueMove set(get_st()):             ";
        vm.set(get_st());
        cout << endl << "TConstValueMove set(get_st()):        ";
        cvm.set(get_st());
        cout << endl << "TPerfectForward set(get_st()):        ";
        pf.set(get_st());
        cout << endl << "TPerfectForwardStrict set(get_st()):  ";
        pft.set(get_st());
        cout << endl << "TMultiVariant set(get_st()):          ";
        mv.set(get_st());
        cout << endl << "TMultiVariantStrict set(get_st()):    ";
        mvt.set(get_st());
    
        cout << endl << "st = 3: ";
        st = 3;
    
        // Test setting with literal
        // Will not compile
        //cout << endl << "TRef set(4): ";
        //rf.set(4);
        cout << endl << "TConstRef set(4):             ";
        crf.set(4);
        cout << endl << "TConstRefAndRvalueRef set(4): ";
        crf_rvf.set(4);
        cout << endl << "TConstRvalueRef set(4):       ";
        crvf2.set(4);
        cout << endl << "TValueMove set(4):            ";
        vm.set(4);
        cout << endl << "TConstValueMove set(4):       ";
        cvm.set(4);
        cout << endl << "TPerfectForward set(4):       ";
        pf.set(4);
        // Will not compile due to SFINAE test
        //cout << endl << "TPerfectForwardStrict set(st): ";
        //pft.set(4);
        cout << endl << "TMultiVariant set(4):         ";
        mv.set(4);
        cout << endl << "TMultiVariantStrict set(4):   ";
        mvt.set(4);
        cout << endl;
    }
    
    int main() {
        test_passing();
    
        return 0;
    }
    
    /* Output:
    
    TStruct st(100): Vc
    TRef (st): Cc-
    TConstRef (st):                Cc-
    TConstRefAndRvalueRef (st):    CcLr-
    TValueMove (st):               CcMcD-
    TConstValueMove (st):          CcCcD-
    TPerfectForward (st):          Cc-
    TPerfectForwardStrict (st):    Cc-
    TMultiVariant (st):            CcPf-
    TMultiVariantStrict (st):      CcPf-
    TConstRef (std::move(st)):             Cc-
    TConstRefAndRvalueRef (std::move(st)): McRr!
    TConstRvalueRef (std::move(st)):       Cc-
    TValueMove (std::move(st)):            McMcD!
    TConstValueMove (std::move(st)):       McCcD!
    TPerfectForward (std::move(st)):       Mc!
    TPerfectForwardStrict (std::move(st)): Mc!
    TMultiVariant (std::move(st)):         McRr!
    TMultiVariantStrict (std::move(st)):   McRr!
    TConstRef (TStruct(6)):               VcCcD
    TConstRefAndRvalueRef (TStruct(6)):    VcMcRrD
    TConstRvalueRef (TStruct(6)):          VcCcD
    TValueMove (TStruct(6)):               VcMcD
    TConstValueMove (TStruct(6)):          VcCcD
    TPerfectForward (TStruct(6)):          VcMcD
    TPerfectForwardStrict (TStruct(6)):    VcMcD
    TMultiVariant (TStruct(6)):            VcMcRrD
    TMultiVariantStrict (TStruct(6)):      VcMcRrD
    TConstRef (get_st()):               VcCcD
    TConstRefAndRvalueRef (get_st()):   VcMcRrD
    TConstRvalueRef (get_st()):         VcCcD
    TValueMove (get_st()):              VcMcD
    TConstValueMove (get_st()):         VcCcD
    TPerfectForward (get_st()):         VcMcD
    TPerfectForwardStrict (get_st()):   VcMcD
    TMultiVariant (get_st()):           VcMcRrD
    TMultiVariantStrict (get_st()):     VcMcRrD
    TConstRef (5):                     VcCcD
    TConstRefAndRvalueRef (5):         VcMcRrD
    TConstRvalueRef (5):               VcCcD
    TValueMove (5):                    VcMcD
    TConstValueMove (5):               VcCcD
    TPerfectForward (5):               Vc
    TMultiVariant (5):                 VcPf
    TMultiVariantStrict (5):           VcMcRrD
    TRef set(st):                      Ca-
    TConstRef set(st):                 Ca-
    TConstRefAndRvalueRef set(st):     SlCa-
    TValueMove set(st):                CcMaD-
    TConstValueMove set(st):           CcCaD-
    TPerfectForward set(st):           Ca-
    TPerfectForwardStrict set(st):     Ca-
    TMultiVariant set(st):             SpCa-
    TMultiVariantStrict set(st):       SpCa-
    TConstRef set(std::move(st)):              Ca-
    TConstRefAndRvalueRef set(std::move(st)):  SrMa!
    TConstRvalueRef set(std::move(st)):        Ca-
    TValueMove set(std::move(st)):             McMaD!
    TConstValueMove set(std::move(st)):        McCaD!
    TPerfectForward set(std::move(st)):        Ma!-
    TPerfectForwardStrict set(std::move(st)):  Ma!
    TMultiVariant set(std::move(st)):          SrMa!
    TMultiVariantStrict set(std::move(st)):    SrMa!
    TConstRef set(TStruct(8)):             VcCaD
    TConstRefAndRvalueRef set(TStruct(8)): VcSrMaD
    TConstRvalueRef set(TStruct(8)):       VcCaD
    TValueMove set(TStruct(8)):            VcMaD
    TConstValueMove set(TStruct(8)):       VcCaD
    TPerfectForward set(TStruct(8)):       VcMaD
    TPerfectForwardStrict set(TStruct(8)): VcMaD
    TMultiVariant set(TStruct(8)):         VcSrMaD
    TMultiVariantStrict set(TStruct(8)):   VcSrMaD
    TConstRef set(get_st()):              VcCaD
    TConstRefAndRvalueRef set(get_st()):  VcSrMaD
    TConstRvalueRef set(get_st()):        VcCaD
    TValueMove set(get_st()):             VcMaD
    TConstValueMove set(get_st()):        VcCaD
    TPerfectForward set(get_st()):        VcMaD
    TPerfectForwardStrict set(get_st()):  VcMaD
    TMultiVariant set(get_st()):          VcSrMaD
    TMultiVariantStrict set(get_st()):    VcSrMaD
    st = 3: Va
    TConstRef set(4):             VcCaD
    TConstRefAndRvalueRef set(4): VcSrMaD
    TConstRvalueRef set(4):       VcCaD
    TValueMove set(4):            VcMaD
    TConstValueMove set(4):       VcCaD
    TPerfectForward set(4):       Va
    TMultiVariant set(4):         SpVa
    TMultiVariantStrict set(4):   VcSrMaD
    DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
    
    */

    Links:
    CppCon 2014: Herb Sutter "Back to the Basics! Essentials of Modern C++ Style"
    PDF presentation
    Meaning of acronym SSO in the context of std::string
    Why is value taking setter member functions not recommended in Herb Sutter's CppCon 2014 talk (Back to Basics: Modern C++ Style)?
    What's the correct enable_if constraint on perfect forwarding setter?
    Beware of Perfect Forwarding Constructors


    Why other variants of passing to constructor or setter are less used


    • T& — cannot accept rvalue. Is the same as "const T&" variant, but less safe.
      class T { 
      C(T& X) : x(X) {} 
      void set(T& X) { 
      x = X; 
      }
      T x;
      };
    • const T&& move — does not allow move due to prohibiting change of passed object. Not used.
      class C { 
      C(const T&& X) : x(std::move(X)) {} 
      void set(const T&& X) { 
      x = std::move(X); 
      }
      T x;
      };
    • const T move — does not allow move due to prohibiting change of passed object.
      class C { 
      C(const T X) : x(std::move(X)) {} 
      void set(const T X) { 
      x = std::move(X); 
      }
      T x;
      };
    • const Perfect forwarding (PF) — will not compile
      class C { 
      template<typename Z>
      C(const Z&& X) : x(std::forward<Z>(X)) {} 
      template<typename Z>
      void set(const Z&& X) { 
      x = std::forward<Z>(X); 
      }
      T x;
      };



    Working with smart pointers


    • Do not pass shared_ptr (or other countref alternatives) by value, if you do not need to count references (reduces performance due to atomic operations with counter) — prefer passing objects by * or & as usual.
    • Do not pass shared_ptr (or other countref alternatives) by reference or const reference, if you do not need to count references — prefer passing objects by * or & as usual.
    • If fabric has to return a polymorphic type (and thus cannot return by value), return unique_ptr, which can be converted to shared_ptr if needed (or return shared_ptr if you are sure that it will always be needed intead of unique_ptr)
    • If a function will need to decide if it wants to copy a shared_ptr or not — you can pass a const shared_ptr& to it
    • Never dereference or call a method of non-local shared_ptr, because this puts object outside of shared_ptr control (instead, first make a local copy of shared_ptr). Example:




    Links:


    CppCon 2014: Herb Sutter "Back to the Basics! Essentials of Modern C++ Style"
    PDF presentation
    What is an 'aliased local shared_ptr' in this example?


    Some of the used images were taken from the linked articles




    Go to Part 1

    Support the author
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 5

      0
      Большое спасибо, Алексей!

      Хотел бы обсудить последний пункт последнего раздела «Never dereference or call a method of non-local shared_ptr, because this puts object outside of shared_ptr control (instead, first make a local copy of shared_ptr)» в сочетании с "// global (static or heap) or aliased local" комментарием в иллюстрирующем коде.

      Далее в иллюстрирующем коде создаётся стабилизирующая локальная копия shared_ptr. Мотив понятен — действия внутри f могут привести к удалению widget-а. Таким образом, этот код иллюстрирует определённый «параноидально-защитный» паттерн программирования: в месте использования определённого типа ссылки (shared_ptr) знай, что безопаснее защититься от reentrancy pitfall.

      Давайте теперь, взяв на вооружение этот совет (т.е., встав на сторону данного «параноидально-защитного паттерна»), рассмотрим первые две рекомендации раздела:

      • Do not pass shared_ptr (or other countref alternatives) by value, if you do not need to count references (reduces performance due to atomic operations with counter) — prefer passing objects by * or & as usual.
      • Do not pass shared_ptr (or other countref alternatives) by reference or const reference, if you do not need to count references — prefer passing objects by * or & as usual.


      Давайте попытаемся совместить логику этих рекомендаций с «параноидально-защитным» паттерном. Caller имеет на руках shared_ptr на widget, который нужно передать в callee (под названием «use» — callee не интересуют манипуляции с ownership widget-а, он просто что-то делает с самим widget-ом). Рекомендовано передать ссылку на widget (то есть в сигнатуре use тип параметра — widget&). Получается, чтобы соблюсти «параноидально-защитный» паттерн, caller перед вызовом use должен удостовериться, что передаваемая ссылка подкреплена локальным инстансом shared_ptr. То есть, если shared_ptr на руках, например, member variable под именем m_p, надо делать так:

      shared_ptr<widget> pStabilized = m_p;
      use(*pStabilized);

      Эту манипуляцию можно было бы выразить короче, если бы (в разрез с первой рекомендацией раздела) параметр use был бы shared_ptr (переданный по значению):

      use(m_p);

      То есть, в случае принятия обоих рекомендаций раздела (первой и последней) caller должен платить за комфорт callee и создавать стабилизирующие локальные копии не-локальных shared_ptr, так?
        0
        Hi. Could you please use english language in comments, so that both the post and the discussion are useful to international audience. So, the question is: should we pass shared_ptr by value or create a local copy and dereference it. I added some links with the source of this recommendation so that you can get more detailed information about it.

        Passing shared_ptr by value obviously looks more simple and «comfortable» as you say in this simple example, but it also has downsides that I can think of, which may be important or not important in your code:
        — you are passing to the function more than it needs (if it just needs a reference) and thus we incapsulate additional logic into that function, making it less general and less eligible for reuse. This function cannot be used for a non-shared_ptr
        — you will have to make copy of shared_ptr with every call of this function if you will need to call it multiple times
        — you will have to make copy of shared_ptr with every call of other functions in this block if you follow this approach

        Also, please note that recommendation is to use auto instead of shared_ptr — you can find out why this is important by following the same link to youtube video.
          0
          Regarding using English — sure why not (I cannot update the initial comment but I'll retell the core concern in brief below). Thanks for the links you provided in the post, I will trace details when I have a better chance (over the weekend, maybe). Thank you for listing arguments for not passing smart pointer arguments in favor of passing & references (stating explicitly that the contract guarantees that the reference is not null always) or * pointers (stating explicitly that the contract allows nullptr reference on some occasions so the callee has to handle that possibility in a meaningful way). I've read Core Guidelines and I agree, but a consideration linked to the last recommendation (see below) leaves a residue so to speak.

          Regarding my core concern on the last section of your post: the last recommendation of that section kind of introduces a complication twist to «prefer using &, * and not smart pointer argument types» rule. Let' get back to my example: I am implementing a class that has a member variable shared_ptr<widget> m_p. It is guaranteed to be not null at a moment and I need to call an external function named «use» that takes widget&. Being a thorough defensive programmer (as the last recommendation teaches me) I have to do the following (now switching to auto):

          auto pStabilized = m_p;
          use(*pStabilized);

          — because from a formal point of view I have no control over what other functions «use» will call. Note that in case of 5 reference arguments of «use» the caller will have 6 lines of code introducing 5 local variables that are used in one line of code — the «use» call. Well, it takes an effort to be a thorough defensive programmer (and never mind the impact on code readability / maintainability). Now imagine that I am not only doing that in my code but also insist on this pattern while doing code reviews…

          Do you agree that this is the right conclusion when all recommendations of the last section are taken into account? If you have a better idea on how to reconcile all recommendations from the last section, please share! I'll do some experiments most likely, but not earlier than over the next weekend.
            0
            As I have said, downsides can be unimportant in your code. Decision has to be made for each particular part of code where you use such an approach.
              0
              In my current projects I am inclined to not follow the last recommendation ("Never dereference or call a method of non-local shared_ptr, because this puts object outside of shared_ptr control (instead, first make a local copy of shared_ptr).").

              That recommendation does not blend well with preceding ones that are not that over-protective or super-defensive. Let it crash, so to speak.

              Your 2-part article is very good, I'll be glad to see more!

      Only users with full accounts can post comments. Log in, please.