Perfect forwarding

This is a discussion on Perfect forwarding within the C++ Programming forums, part of the General Programming Boards category; Consider this code: Code: #include <iostream> #include <string> #include <algorithm> #include <iterator> #include <cctype> #include <fstream> #include <vector> #include <type_traits> ...

  1. #1
    C++まいる!Cをこわせ! Elysia's Avatar
    Join Date
    Oct 2007
    Posts
    22,548

    Perfect forwarding

    Consider this code:
    Code:
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    
    template<typename T>
    auto lmove(T& arg) -> typename std::remove_reference<T>::type&
    {
    	return static_cast<typename std::remove_reference<T>::type&>(arg);
    };
    
    template<typename T>
    auto lmove(const T& arg) -> const typename std::remove_reference<T>::type&
    {
    	return static_cast<const typename std::remove_reference<T>::type&>(arg);
    };
    
    template<typename T> std::string print_me() { return ""; }
    template<> std::string print_me<std::vector<int>>() { return "bar<std::vector<int>>"; }
    template<> std::string print_me<std::string>() { return "bar<std::string>"; }
    
    template<typename T>
    struct bar
    {
    	T m;
    	template<typename... Args_t> bar(Args_t... args): m(args...) {}
    
    	bar(const bar& that): m(that.m) { std::cout << print_me<T>() << "::bar(const bar&)\n"; }
    	bar(const bar&& that): m(std::move(that.m)) { std::cout << print_me<T>() << "::bar(const bar&&)\n"; }
    };
    
    bar<std::string> Clone(bar<std::string>& str) 
    {
    	std::cout << "Clone(std::string):\n";
    	bar<std::string> tmp(str);
    	return tmp;
    }
    
    bar<std::vector<int>> Clone(bar<std::vector<int>>& v)
    {
    	std::cout << "Clone(std::vector<int>):\n";
    	bar<std::vector<int>> tmp(v);
    	return tmp;
    }
    
    void foo5(bar<std::vector<int>>& v, bar<std::string>& s)
    {
    	std::cout << "foo4:\n";
    	std::cout << "v[0]: " << v.m[0] << ", v[1]: " << v.m[1] << ", v[2]: " << v.m[2] << "\n"
    		"s: " << s.m <<  "\n";
    }
    
    template<typename... Args_t>
    void foo4(Args_t... args)
    {
    	std::cout << "foo4:\n";
    	foo5(args...);
    }
    
    template<typename... Args_t>
    void foo3(Args_t... args)
    {
    	std::cout << "foo3:\n";
    	foo4<bar<std::vector<int>>&, bar<std::string>&>(args...);
    }
    
    template<typename... Args_t>
    void foo2(Args_t... args)
    {
    	std::cout << "foo2:\n";
    	foo3<bar<std::vector<int>>&, bar<std::string>&>(args...);
    	//foo3<decltype(args)...>(args...);
    }
    
    template<typename... Args_t>
    void foo1(Args_t... args)
    {
    	std::cout << "foo1:\n";
    	foo2(Clone(args)...);
    }
    
    int main() 
    {
    	std::cout << "main:\n";
    	bar<std::vector<int>> v;
    	bar<std::string> s("Hello World!");
    	v.m.push_back(1);
    	v.m.push_back(2);
    	v.m.push_back(3);
    	foo1<decltype(lmove(v)), decltype(lmove(s))>(v, s);
    	//foo1(v, s);
    }
    With this mockup code, I get the output:

    main:
    foo1:
    Clone(std::string):
    bar<std::string>::bar(const bar&)
    Clone(std::vector<int>):
    bar<std::vector<int>>::bar(const bar&)
    foo2:
    foo3:
    foo4:
    foo4:
    v[0]: 1, v[1]: 2, v[2]: 3
    s: Hello World!

    Just like I want to. Perfect forwarding. But the problem is that I have to explicitly specify the template parameters for the calls to foo3 and foo4, as well as foo1:

    foo4<bar<std::vector<int>>&, bar<std::string>&>(args...);
    foo3<bar<std::vector<int>>&, bar<std::string>&>(args...);
    foo1<decltype(lmove(v)), decltype(lmove(s))>(v, s);

    The problem is how to get rid of these to get this perfect forwarding where unnecessary calls to copy and move constructors are eliminated.

    As you can see, I've thought to use

    foo3<decltype(args)...>(args...);
    foo4<decltype(args)...>(args...);

    for foo3 and foo4, which compiles fine with Clank (output not inspected), but fails to compile with Visual Studio. Since I am currently stuck with Visual Studio, I need this to compile with Microsoft's compiler, too.
    So, any suggestions on workarounds or ideas on how to make this work?

    (This is a dumbed down example from real code, so the structure of Clone, etc must be there.)
    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.

  2. #2
    Registered User whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    7,667
    I'm just confirming, are these the errors, or am I doing something wrong?
    Code:
    1>------ Build started: Project: perffwd, Configuration: Debug Win32 ------
    1>  perffwd.cpp
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(32): error C2143: syntax error : missing ',' before '...'
    1>          c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(36) : see reference to class template instantiation 'bar<T>' being compiled
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(32): error C2061: syntax error : identifier 'Args_t'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(32): error C2143: syntax error : missing ',' before '...'
    1>          c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(39) : see reference to class template instantiation 'bar<T>' being compiled
    1>          with
    1>          [
    1>              T=std::string
    1>          ]
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(32): error C2061: syntax error : identifier 'Args_t'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(32): error C2143: syntax error : missing ',' before '...'
    1>          c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(46) : see reference to class template instantiation 'bar<T>' being compiled
    1>          with
    1>          [
    1>              T=std::vector<int>
    1>          ]
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(32): error C2061: syntax error : identifier 'Args_t'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(59): error C2143: syntax error : missing ',' before '...'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(60): error C2065: 'Args_t' : undeclared identifier
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(60): error C2143: syntax error : missing ')' before '...'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(60): error C2182: 'foo4' : illegal use of type 'void'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(60): error C2059: syntax error : ')'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(68): error C2143: syntax error : missing ';' before '{'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(68): error C2447: '{' : missing function header (old-style formal list?)
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(73): error C2143: syntax error : missing ',' before '...'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(74): error C2065: 'Args_t' : undeclared identifier
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(74): error C2143: syntax error : missing ')' before '...'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(74): error C2182: 'foo2' : illegal use of type 'void'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(74): error C2059: syntax error : ')'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(83): error C2143: syntax error : missing ';' before '{'
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(83): error C2447: '{' : missing function header (old-style formal list?)
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(93): error C2065: 'v' : undeclared identifier
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(94): error C2065: 'v' : undeclared identifier
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(95): error C2065: 'v' : undeclared identifier
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(96): error C2065: 'foo1' : undeclared identifier
    1>c:\users\josh2\documents\visual studio 2010\projects\perffwd\perffwd\perffwd.cpp(96): error C2062: type '' unexpected
    ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
    I compiled it with MSVC 2010 Express...

  3. #3
    C++まいる!Cをこわせ! Elysia's Avatar
    Join Date
    Oct 2007
    Posts
    22,548
    You need the experimental compiler for 2012:
    Download Visual C++ Compiler November 2012 CTP from Official Microsoft Download Center

    2010 and 2012 (out of the box) doesn't support variadic templates.
    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
    May 2010
    Posts
    2,711
    Note after removing a couple of extra semicolons the program also compiles with g++ version 4.8.0

    main.cpp|16|error: extra ‘;’ [-Wpedantic]|
    main.cpp|22|error: extra ‘;’ [-Wpedantic]|
    main.cpp||In function ‘bar<std::basic_string<char> > Clone(bar<std::basic_string<char> >&)’:|
    main.cpp|38|warning: no previous declaration for ‘bar<std::basic_string<char> > Clone(bar<std::basic_string<char> >&)’ [-Wmissing-declarations]|
    main.cpp||In function ‘bar<std::vector<int> > Clone(bar<std::vector<int> >&)’:|
    main.cpp|45|warning: no previous declaration for ‘bar<std::vector<int> > Clone(bar<std::vector<int> >&)’ [-Wmissing-declarations]|
    main.cpp||In function ‘void foo5(bar<std::vector<int> >&, bar<std::basic_string<char> >&)’:|
    main.cpp|52|warning: no previous declaration for ‘void foo5(bar<std::vector<int> >&, bar<std::basic_string<char> >&)’ [-Wmissing-declarations]|
    ||=== Build finished: 2 errors, 3 warnings ===|
    Jim

  5. #5
    C++まいる!Cをこわせ! Elysia's Avatar
    Join Date
    Oct 2007
    Posts
    22,548
    What "version" of the program? Both with and without decltype in foo3/4 works with Clang, but decltype for foo3/4 does not work with Visual Studio, which is the problem.
    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
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    4,231
    O_o

    Welcome to my world; pushing the edge of language features is often a path paved with difficulty.

    *shrug*

    I did give you fair warning that just because C++11 was a standard didn't grant you a pass to use every C++11 feature as you found fit.

    It will be a couple of more years yet before a true standard for using C++11 features reveals itself.

    Anyway, if your target compiler doesn't like expanding variable template arguments with `decltype', you only have the one option: remove `decltype' from the equation.

    Now, you can do this in several ways.

    I suggest cutting out the "middle man" with function signatures that are appropriate to the purpose because you are employing "perfect forwarding" which is "spelled" with a combination of references. I have provided here an example.

    However, there is another option that is more reliable, but much more difficult to implement. You will need to employee a meta-programming facility, meta-template even, to "fill out" the types involved in the parameters by using recursion over template types which will ultimately return a pointer to the expanded function to call. I only list this for completeness sake; the fact is, you aren't going to want to do that; it requires you to manually unroll every argument for every template function in the chain which trashes the simplicity of using "rvalue references" and "variadic templates"; if you are willing to throw that much work at it, you can also eliminate the reliance on "rvalue references" and "variadic templates" making that a better approach because of the insignificant bit of extra code involved in making the entire interface C++98 compatible. I did not include an example of this because I know you aren't going to do it.

    I am aware of a third option which you'll find more palatable. Thanks to the way overload resolution works in the face of templates and pointers to functions you can use the type to "bind" the target function template to the type of function you actually wish to call without actually specifying the template expansion. (This is basically reversing how `auto' works.) You'll still need a meta-programming facility, but instead of returning a pointer to a specific function, which requires expanding the function as you go, you only need to return the type of the function to expand which is generalized simply because the actual target function isn't involved in the expansion. You can find the pieces needed to do this in my other posts where I use template recursion to build a generalized `switch' like facility during compilation. [Edit]To be clear, this is a meta-programming facility; it increases the time it takes to compiler code, but no recursion takes place during program execution.[/Edit]

    Soma

    Code:
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    
    template<typename T>
    auto lmove(T& arg) -> typename std::remove_reference<T>::type&
    {
        return static_cast<typename std::remove_reference<T>::type&>(arg);
    };
    
    template<typename T>
    auto lmove(const T& arg) -> const typename std::remove_reference<T>::type&
    {
        return static_cast<const typename std::remove_reference<T>::type&>(arg);
    };
    
    template<typename T> std::string print_me() { return ""; }
    template<> std::string print_me<std::vector<int>>() { return "bar<std::vector<int>>"; }
    template<> std::string print_me<std::string>() { return "bar<std::string>"; }
    
    template<typename T>
    struct bar
    {
        T m;
        template<typename... Args_t> bar(Args_t... args): m(args...) {}
    
        bar(const bar& that): m(that.m) { std::cout << print_me<T>() << "::bar(const bar&)\n"; }
        bar(const bar&& that): m(std::move(that.m)) { std::cout << print_me<T>() << "::bar(const bar&&)\n"; }
    };
    
    bar<std::string> Clone(const bar<std::string>& str)
    {
        std::cout << "Clone(std::string):\n";
        bar<std::string> tmp(str);
        return tmp;
    }
    
    bar<std::vector<int>> Clone(const bar<std::vector<int>>& v)
    {
        std::cout << "Clone(std::vector<int>):\n";
        bar<std::vector<int>> tmp(v);
        return tmp;
    }
    
    void foo5(bar<std::vector<int>>& v, bar<std::string>& s)
    {
        std::cout << "foo4:\n";
        std::cout << "v[0]: " << v.m[0] << ", v[1]: " << v.m[1] << ", v[2]: " << v.m[2] << "\n"
            "s: " << s.m <<  "\n";
    }
    
    template<typename... Args_t>
    void foo4(Args_t &... args) // This operates on references by design.
    {                           // We have generated a chain of references to an anonymously cloned
                                // temporary allowing us to modify the parameters as we like.
        std::cout << "foo4:\n";
        foo5(args...);
    }
    
    template<typename... Args_t>
    void foo3(Args_t &&... args) // This operates on references by design as we are forwarding to this function.
    {                            // In this way, it is no different than a constructor.
        std::cout << "foo3:\n";
        //foo4<bar<std::vector<int>>&, bar<std::string>&>(args...);
        foo4(args...);
    }
    
    template<typename... Args_t>
    void foo2(Args_t && ... args) // This operates on references by design as we are forwarding to this function.
    {                             // In this way, it is no different than a constructor.
        std::cout << "foo2:\n";
        //foo3<bar<std::vector<int>>&, bar<std::string>&>(args...);
        foo3(args...);
    }
    
    template<typename... Args_t>
    void foo1(const Args_t &... args) // This does not need to modify the types.
    {                                 // It exists as an interface cloning arguments for later use.
        std::cout << "foo1:\n";
        foo2(Clone(args)...);
    }
    
    int main()
    {
        std::cout << "main:\n";
        bar<std::vector<int>> v;
        bar<std::string> s("Hello World!");
        v.m.push_back(1);
        v.m.push_back(2);
        v.m.push_back(3);
        foo1(v, s);
    }

  7. #7
    C++まいる!Cをこわせ! Elysia's Avatar
    Join Date
    Oct 2007
    Posts
    22,548
    Now that was a very nice and clean solution which solved all problems, which I actually didn't think of.
    So, appending & after the type to specify a reference would expand it just like in the normal case where we didn't use variadic templates.
    I'm attaching the revised code to help others if they face the same dilemma:

    Code:
    #include <iostream>
    #include <string>
    #include <algorithm>
    #include <iterator>
    #include <cctype>
    #include <fstream>
    #include <vector>
    #include <type_traits>
    #include <typeinfo>
    #include <utility>
    
    template<typename T> std::string print_me() { return ""; }
    template<> std::string print_me<std::vector<int>>() { return "bar<std::vector<int>>"; }
    template<> std::string print_me<std::string>() { return "bar<std::string>"; }
    
    template<typename T>
    struct bar
    {
    	T m;
    	template<typename... Args_t> bar(Args_t... args): m(args...) {}
    
    	bar(const bar& that): m(that.m) { std::cout << print_me<T>() << "::bar(const bar&)\n"; }
    	bar(const bar&& that): m(std::move(that.m)) { std::cout << print_me<T>() << "::bar(const bar&&)\n"; }
    };
    
    bar<std::string> Clone(bar<std::string>& str) 
    {
    	std::cout << "Clone(std::string):\n";
    	bar<std::string> tmp(str);
    	return tmp;
    }
    
    bar<std::vector<int>> Clone(bar<std::vector<int>>& v)
    {
    	std::cout << "Clone(std::vector<int>):\n";
    	bar<std::vector<int>> tmp(v);
    	return tmp;
    }
    
    void foo5(bar<std::vector<int>>& v, bar<std::string>& s)
    {
    	std::cout << "foo4:\n";
    	std::cout << "v[0]: " << v.m[0] << ", v[1]: " << v.m[1] << ", v[2]: " << v.m[2] << "\n"
    		"s: " << s.m <<  "\n";
    }
    
    template<typename... Args_t>
    void foo4(Args_t&... args)
    {
    	std::cout << "foo4:\n";
    	foo5(args...);
    }
    
    template<typename... Args_t>
    void foo3(Args_t&... args)
    {
    	std::cout << "foo3:\n";
    	foo4(args...);
    }
    
    template<typename... Args_t>
    void foo2(Args_t&&... args)
    {
    	std::cout << "foo2:\n";
    	foo3(args...);
    }
    
    template<typename... Args_t>
    void foo1(Args_t&... args)
    {
    	std::cout << "foo1:\n";
    	foo2(Clone(args)...);
    }
    
    int main() 
    {
    	std::cout << "main:\n";
    	bar<std::vector<int>> v;
    	bar<std::string> s("Hello World!");
    	v.m.push_back(1);
    	v.m.push_back(2);
    	v.m.push_back(3);
    	foo1(v, s);
    }
    Thank you, Soma. You made my day!
    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. TCP data forwarding
    By Labor in forum Linux Programming
    Replies: 3
    Last Post: 05-24-2011, 01:58 PM
  2. Forwarding connection
    By splintter in forum C Programming
    Replies: 1
    Last Post: 08-28-2010, 12:17 PM
  3. Forwarding client data
    By zacs7 in forum Networking/Device Communication
    Replies: 4
    Last Post: 02-16-2010, 10:15 PM
  4. Port Forwarding
    By cerin in forum Tech Board
    Replies: 1
    Last Post: 04-05-2007, 03:41 PM
  5. Port Forwarding DI-624
    By Tonto in forum Tech Board
    Replies: 0
    Last Post: 08-27-2006, 10:48 PM

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21