Thread: C++ Primer exercise 16.47

  1. #1
    Registered User
    Join Date
    Sep 2015
    Location
    Italy
    Posts
    38

    Question C++ Primer exercise 16.47

    I'm a bit confused about type conversions in this exercise so I hope someone will enlighten me. C++ is a statically typed language therefore types must match otherwise conversion rules must be provided. This exercise illustrate how to preserve type informations using the function template std::forward, for better understanding I played with the code changing it to test different scenarios and simplifying I wrote this code:
    Code:
    #include <iostream>
    #include <utility>
    
    using namespace std;
    
    template <typename F, typename T1, typename T2>
    void flip(F f, T1 &&t1, T2 &&t2)
    {
            // f(t2, t1);   // error: it calls g(int, int)
            f(forward<T2>(t2), forward<T2>(t1));
    }
    
    void g(int&& i, int&& j)        {cout << i << " " << j << endl; }
    
    int main()
    {
            int z(0);
            flip(g, 42, z);
            g(42,42);
    
            return EXIT_SUCCESS;
    }
    std::forward return a rvalue reference.
    1) "&&i" isn't bound to "t2" (a lvalue) but to a rvalue reference of "z" therefore any further use of "z" value must be avoided.
    2) "&&i" is bound to the rvalue reference of std::forward therefore any further use of "t2" must be avoided ("z" lvalue not moved). And the same applies to "t1"
    Which of these two answers is correct?
    Thank you in advance

  2. #2
    Informer -Adrian's Avatar
    Join Date
    Jan 2013
    Posts
    830
    Are those answers given in the book, or are they yours? Answer 1 is definitely wrong. You're passing z (an lvalue) as the argument for t2, which it then cannot take by rvalue reference, unlike the rvlaue 42 passed to t1.

    I tried your example and it doesn't even compile here, because you only defined flip with rvalue references as parameters. So I don't think answer 2 is relevant in light of that road block.

    You could use std::move on z, but I don't suppose that's the purpose of the exercise.

  3. #3
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by -Adrian View Post
    ...You're passing z (an lvalue) as the argument for t2, which it then cannot take by rvalue reference...
    && when used as template parameters aren't rvalue references, they're known as universal references (now called forwarding references, I think?). They're different because they can bind both rvalue and lvalue references.

    I tried your example and it doesn't even compile here, because you only defined flip with rvalue references as parameters...
    That has nothing to do with it. Like I said, && in template functions can bind to both lvalue and rvalue references. The problem(s) are that

    1) You're trying to forward an lvalue reference (t2) to g, which only takes an rvalue reference.
    2) You're trying to forward t1 as an lvalue reference while it is actually a rvalue reference (because you typed T2 instead of T1, its actual type).

    As for the questions, I have no idea what they're supposed to mean (what is &&i???)...
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  4. #4
    Registered User
    Join Date
    Sep 2015
    Location
    Italy
    Posts
    38
    Yep! my mistakes (I played with the code too much) the definition of g function at line 13 has changed, then I correct the typo at line 10 (thanks Elysia) in second parameter of the function pointer f, T2 has changed to T1:
    Code:
    #include <iostream>
    #include <utility>
    
    using namespace std;
    
    template <typename F, typename T1, typename T2>
    void flip(F f, T1 &&t1, T2 &&t2)
    {
            // f(t2, t1);   // error: it calls g(int, int)
            f(std::forward<T2>(t2), std::forward<T1>(t1));
    }
    
    void g(int&& i, int& j) {cout << i << " " << j << endl; }
    
    int main()
    {
            int z(0);
            flip(g, z, 42);
    
            return EXIT_SUCCESS;
    }
    Now it's all as the book illustrate, because of these changes I'll reformulate possible answers (@~Adrian: those are mine not from the book):
    1) The first parameter of g function (&&i) isn't bound to "t2" (a lvalue) but to the rvalue literal "42". The second parameter of g function (&j) is bound to the rvalue reference of std::forward therefore any further use of "t1" must be avoided ("z" object untouched).
    2) The first parameter of g function (&&i) isn't bound to "t2" (a lvalue) but to the rvalue literal "42". The second parameter of g function (&j) isn't bound to "t1" (although it can) but to "z" object therefore any further use of "t1" value is allowed.
    Which one is it right?
    Thank you again

  5. #5
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    forward just takes the type of the parameter and passes along the "correct" type, so if T is an lvalue, then the type returned by forward is an lvalue. If T is an rvalue, forward returns an rvalue.
    (Perhaps confusingly enough, if you pass an rvalue directly, it actually becomes an lvalue, which is why the first g call fails.)
    So since t1 is bound to z and z is passed as an lvalue, which answer do you think is right?
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  6. #6
    Registered User
    Join Date
    Sep 2015
    Location
    Italy
    Posts
    38
    The number 2 because &j is bound to z passed as lvalue via std::forward
    Any further comments?

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You're right. Answer #1 doesn't make sense. "z" is passed as an lvalue reference to flip, which forwards it as an lvalue reference to g, so #1 doesn't make sense. IF it made sense, you would have a compile error, since g accepts only an lvalue reference. Any questions about that?
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    Registered User
    Join Date
    Sep 2015
    Location
    Italy
    Posts
    38

    Exclamation

    No, nothing to say it's clear now. Instead could I have confirmation about that std::forward act as std::move on t2?
    t2 is bound to the literal 42 that is a rvalue therefore std::forward returning rvalues it behaves as std::move on t2 and any further access to t2's value inside flip is unsafe, am I right?
    code:
    Code:
    template <typename F, typename T1, typename T2>
    void flip(F f, T1 &&t1, T2 &&t2)
    {
            // f(t2, t1);   // error: it calls g(int, int)
            f(std::forward<T2>(t2), std::forward<T1>(t1));
            int val = t2;   // <------ wrong? 
    }

  9. #9
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by frank67 View Post
    t2 is bound to the literal 42 that is a rvalue therefore std::forward returning rvalues it behaves as std::move on t2 and any further access to t2's value inside flip is unsafe, am I right?
    You're right. forward does indeed behave like move on t2 since it's an rvalue reference. Therefore, accessing it after the call to f is unsafe.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  10. #10
    Informer -Adrian's Avatar
    Join Date
    Jan 2013
    Posts
    830
    Quote Originally Posted by Elysia View Post
    && when used as template parameters aren't rvalue references, they're known as universal references (now called forwarding references, I think?). They're different because they can bind both rvalue and lvalue references.
    Thanks for the insight Elysia. I don't want to hijack this thread, but could you elaborate on what you meant with:
    Quote Originally Posted by Elysia View Post
    (Perhaps confusingly enough, if you pass an rvalue directly, it actually becomes an lvalue, which is why the first g call fails.)
    Do you mean that t1 and t2 are lvalues no matter what's passed to flip, or is this about something else?

  11. #11
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Quote Originally Posted by -Adrian View Post
    Do you mean that t1 and t2 are lvalues no matter what's passed to flip, or is this about something else?
    Yes. Consider:

    Code:
    void foo(int&& a)
    {
        bar(a);
    }
    
    void bar(int&&)
    {
    }
    This actually results in a compile error, although one might think it shouldn't. Since a's type is int &&, why shouldn't we be able to pass it to bar just like we do with any other variable? Well, the standard disagrees. I've been pretty loose with my definitions of lvalues, rvalues and lvalues references, rvalue references. While we can call foo with an rvalue, that rvalue is bound to the rvalue reference a. But "a" is a variable, just like any other. The variable itself is NOT an rvalue, so when you pass it to a function, it actually becomes an lvalue! An lvalue cannot be bound to an rvalue reference, so the above fails to compile.

    To actually make "a" into an rvalue, you have to use std::move, e.g.: bar(std::move(a));
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. c++ primer exercise 16.12
    By frank67 in forum C++ Programming
    Replies: 18
    Last Post: 09-30-2015, 03:32 PM
  2. c++ primer exercise 15.31
    By frank67 in forum C++ Programming
    Replies: 10
    Last Post: 09-23-2015, 09:38 AM
  3. c++ primer exercise 15.26
    By frank67 in forum C++ Programming
    Replies: 2
    Last Post: 09-18-2015, 09:34 AM
  4. Need help with an exercise from the book "C++ Primer"
    By vizbasic2010 in forum C++ Programming
    Replies: 3
    Last Post: 06-07-2012, 07:27 AM
  5. C++ Primer 4th Edition, Problem with Exercise
    By Kaidao in forum C++ Programming
    Replies: 4
    Last Post: 07-15-2006, 11:13 AM