Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template type deduction for stream manipulators

I'm unsure as to whether this code will not compile.

The example code I'm working with:

#include <iostream>
using std::cout;
using std::endl;

class Foo {
    public:
        template<typename T>
        Foo& operator<<(const T& t) {
            cout << t;
            return *this;
        }
};

int main() {
    Foo foo;
    foo << "Hello World"; // perfectly fine
    foo << endl; // shit hits the fan

    return 0;
}

This is the error:

test.cpp:19:12: error: no match for ‘operator<<’ in ‘foo << std::endl’
test.cpp:19:12: note: candidates are:
test.cpp:10:14: note: template<class T> Foo& Foo::operator<<(const T&)
test.cpp:10:14: note:   template argument deduction/substitution failed:
test.cpp:19:12: note:   couldn't deduce template parameter ‘T’

I'm confused as to why it cannot substitute the function type of endl (ostream& (*)(ostream&)) for T, where it clearly is fine with doing it when you specify cout << endl;

I find it additionally puzzling that this fixes the problem [ edited ]

Foo& operator<<(ostream& (*f)(ostream&)) {
    cout << f;
    return *this;
}

In case the question isn't clear, I'm asking why it could not deduce the template in the first place.

like image 508
Anthony Sottile Avatar asked Feb 23 '13 16:02

Anthony Sottile


People also ask

What is template argument deduction?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.

What are template arguments?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.

What is type deduction C++?

Type inference or deduction refers to the automatic detection of the data type of an expression in a programming language. It is a feature present in some strongly statically typed languages. In C++, the auto keyword(added in C++ 11) is used for automatic type deduction.


2 Answers

endl is a manipulator, i.e. it's an unresolved function type. There are several overloads, and the type deduction is unable to decide which one you want.

More specificly, here's what endl looks like (in GNU libc++):

/**
 *  @brief  Write a newline and flush the stream.
 *
 *  This manipulator is often mistakenly used when a simple newline is
 *  desired, leading to poor buffering performance.  See
 *  http://gcc.gnu.org/onlinedocs/libstdc++/manual/bk01pt11ch25s02.html
 *  for more on this subject.
*/
template<typename _CharT, typename _Traits>
  inline basic_ostream<_CharT, _Traits>&
  endl(basic_ostream<_CharT, _Traits>& __os)
  { return flush(__os.put(__os.widen('\n'))); }

Updated So, the problem is, the compiler cannot deduce which instance of endl you would be passing (it's an unresolved overload). You might work around this by doing a static_cast<ostream&(*)(ostream&)>(endl) instead.

Of course, that's not convenient. Here's a simple fix: http://liveworkspace.org/code/2F2VHe$1

#include <iostream>
using std::cout;
using std::endl;

class Foo : public std::ostream
{
    public:
        template<typename T>
        Foo& operator<<(T&& t) {
            cout << std::forward<T>(t);
            return *this;
        }

        typedef std::ostream& (manip)(std::ostream&);

        Foo& operator<<(manip& m) {
            cout << m;
            return *this;
        }
};

int main() {
    Foo foo;
    foo << "Hello World"; // perfectly fine
    foo << endl; // everything is fine

    return 0;
}
like image 194
sehe Avatar answered Oct 01 '22 19:10

sehe


The problem is that endl is a manipulator defined as a function template. Paragraph 27.7.1 of the C++11 Standard specifies its signature:

template <class charT, class traits>
basic_ostream<charT,traits>& endl(basic_ostream<charT,traits>& os);
template <class charT, class traits>

Moreover, per Paragraph 13.3.1 on overload resolution:

In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction (14.8.3, 14.8.2). Those candidates are then handled as candidate functions in the usual way.

Your operator << is a defined as a template, and the compiler needs to deduce the type of T. However, how can the compiler know which instantiation of endl you meant? How can it deduce the template arguments charT and traits? There is nothing else in your call to operator << from which it could be deduced.

You have two ways out of this problem. Either you cast the type of endl explicitly, to tell the compiler which overload shall be picked:

foo << (std::ostream& (*)(std::ostream&))endl;

Or, as you did, you create an overload of operator << which accepts a function with that specific signature. Your compiler will now select it:

Foo& operator<<(ostream& (*f)(ostream&)) 
{
    return *this << f;
}

Inside this function definition there is no ambiguity as to what f is: its type is precisely defined. However, be careful here: this function doesn't likely do what you expect! In fact, it just keeps calling itself, generating an infinite recursion!

Therefore, this assertion:

[...] note I'm actually calling my other method implementation:

Is incorrect: you're not calling the other method implementation, you keep calling the same function over and over again.

like image 44
Andy Prowl Avatar answered Oct 01 '22 17:10

Andy Prowl