CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 5 of 5
  1. #1
    Lindley is offline Elite Member Power Poster
    Join Date
    Oct 2007
    Location
    Seattle, WA
    Posts
    10,895

    Variadic templates and overload resolution

    I'm trying to learn how to use variadic templates, and I decided a great example would be serializing a series of types into a stringstream:

    Code:
            // Send a fully constructed message.
            virtual void send(ostringstream &msg) = 0;
    
            // Construct a message from the arguments and send it.
            // This is the usual entry point.
            template <typename ...Args>
            void send(Args ...args)
            {
                ostringstream  msg;
                send(msg,args...);
            }
    
            // Finish constructing a message and then send it.
            template <typename T, typename ...Args>
            void send(ostringstream &msg, const T& nextpart, Args ...args)
            {
                msg << nextpart;
                send(msg,args...);
            }
    This works fine, so far as I can tell. However, I decided to see if I could specialize the way certain types are serialized. I tried using a Google Protocol Buffer object as an example, and added this:

    Code:
            // Handle a protocol buffer type while constructing a message.
            template <typename ...Args>
            void send(ostringstream &msg,
                      const google::protobuf::MessageLite &protobuf, Args ...args)
            {
                std::string msg_str = protobuf.SerializeAsString();
                msg << msg_str;
                send(msg,args...);
            }
    I would expect this overload to be preferred over the generic T overload when a protobuf object (which always inherits from MessageLite) is passed into send() anywhere in the list. However, this is not happening. I am getting an error message to the effect that << doesn't know how to deal with my concrete type, pointing at the T overload.

    Any insight into what's going on here?

  2. #2
    Join Date
    Oct 2008
    Posts
    1,456

    Re: Variadic templates and overload resolution

    Quote Originally Posted by Lindley View Post
    Any insight into what's going on here?
    the same thing that's going on here:

    Code:
    struct A{};
    struct B:A{};
    
    template < class T > void f(T);
    void f(A const&);
    
    int main() { B b; f(b); } // calls f(T)
    that is exactly what I would expect, unless I'm missing something. You should constrain the template version to types having a suitable stream operator, something like:

    Code:
    #include <sstream>
    
    struct A{};
    struct B:A{};
    
    void helper(...);
    template< class T > auto helper(T&& t) -> decltype( std::declval<std::ostringstream&>() << std::forward<T>(t) );
    
    template < class T, class V = decltype(helper(std::declval<T>())) >
    struct is_ostringstreamable: std::false_type {};
    
    template < class T >
    struct is_ostringstreamable<T,std::ostream&>: std::true_type {};
    
    template < class T >
    typename std::enable_if< is_ostringstreamable<T>::value >::type f(T&& t);
    void f(A const&);
    
    int main() { B b; f(b); } // calls f(A)
    or the simpler but less readable/usable:

    Code:
    #include <sstream>
    
    struct A{};
    struct B:A{};
    
    template < class T >
    auto f(T&& t) -> decltype( (void)( std::declval<std::ostringstream&>() << std::forward<T>(t) ) );
    void f(A const&);
    
    int main() { B b; f(b); } // calls f(A)
    Anyway, specializing a variadic function template via overloading seems looking for troubles to me; even using SFINAE, things can get complicated soon when more and more complex overloads are added.

    BTW, why are you passing by value in the send functions ? perfect forwarding seems more appropriate to me.

  3. #3
    Lindley is offline Elite Member Power Poster
    Join Date
    Oct 2007
    Location
    Seattle, WA
    Posts
    10,895

    Re: Variadic templates and overload resolution

    Code:
    the same thing that's going on here:
    So what you're saying is that during overload resolution, exact matches are preferred over template matches, but template matches are preferred over base class matches? That's slightly annoying but I can believe it.

    BTW, why are you passing by value in the send functions ? perfect forwarding seems more appropriate to me.
    Please clarify what syntax you would suggest. I don't have a complete understanding of perfect forwarding yet.

    even using SFINAE, things can get complicated soon when more and more complex overloads are added.
    Ah, SFINAE. I could probably do what I want using std::enable_if and std::is_base_of. However, I'd prefer not to have to *negate* every single special case in the default overload, which I'm worried might be necessary if I go down that route.
    Last edited by Lindley; January 3rd, 2014 at 10:56 AM.

  4. #4
    2kaud's Avatar
    2kaud is offline Super Moderator Power Poster
    Join Date
    Dec 2012
    Location
    England
    Posts
    7,822

    Re: Variadic templates and overload resolution

    So what you're saying is that during overload resolution, exact matches are preferred over template matches, but template matches are preferred over base class matches? That's slightly annoying but I can believe it.
    Yes. Extending the test program from post #2
    Code:
    #include <iostream>
    using namespace std;
    
    struct A{};
    struct B:A{};
    
    void f(A const&)
    {
    	cout << "A\n";
    }
    
    template < class T >
    void f(T const &)
    {
    	cout << "T template\n";
    }
    
    int main()
    {
    A a;
    B b; 
    
    	cout << "f(a) ";
    	f(a);
    
    	cout << "f(b) ";
    	f(b);
    }
    gives the output
    Code:
    f(a) A
    f(b) T template
    This is as expected. f(b) will instantiate f(T const &) to be f(B const &) which is a better resolution to f(b) than f(A const &).
    All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!

    C++23 Compiler: Microsoft VS2022 (17.6.5)

  5. #5
    Join Date
    Oct 2008
    Posts
    1,456

    Re: Variadic templates and overload resolution

    Quote Originally Posted by Lindley View Post
    Please clarify what syntax you would suggest. I don't have a complete understanding of perfect forwarding yet.[...]However, I'd prefer not to have to *negate* every single special case in the default overload, which I'm worried might be necessary if I go down that route.
    there's no need to negate, as I suggested above, just restrict the primary overload to streamable types, something like ( not tested ):

    Code:
    public:
    
            // Construct a message from the arguments and send it.
            // This is the usual entry point.
            template <typename ...Args>
            void send(Args&& ...args)
            {
                ostringstream  msg;
                on_send(msg,std::forward<Args>(args)...);
            }
    
    private:
    
            // Send a fully constructed message.
            virtual void on_send(ostringstream &msg) = 0;
    
            // parse an ostringstrem-able type
            template <typename T, typename ...Args>
            typename std::enable_if< is_ostringstreamable<T>::value >::type
            	on_send(ostringstream &msg, T const& nextpart, Args&& ...args)
            {
                msg << nextpart;
                on_send(msg,std::forward<Args>(args)...);
            }
    
            // parse a protobuf
            template <typename ...Args>
            void on_send(ostringstream &msg, google::protobuf::MessageLite const& protobuf, Args&& ...args)
            {
                std::string msg_str = protobuf.SerializeAsString();
                msg << msg_str;
                on_send(msg,std::forward<Args>(args)...);
            }
    
            // parse a MyBase
            template <typename ...Args>
            void on_send(ostringstream &msg, MyBase const& , Args&& ...args)
            {
                // ...
                on_send(msg,std::forward<Args>(args)...);
            }
    
            // ... add other overloads here, no change is needed if overload is obtained via implicit upcasting as above
            // or if you don't need to overload for types that already defines their stream operators ...
    BTW, note that your original code will suffer of the same problem you experienced if an ostringstream subclass is used ...
    Last edited by superbonzo; January 3rd, 2014 at 01:29 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured