std::function as a custom stream manipulator
Asked Answered
S

3

11

I'm trying to use C++11 features to make custom stream manipulators easier to create. I can use lambda functions as manipulators, but not std::function<ostream&(ostream&)>.

Here's the code, boiled down:

#include <iostream>
#include <functional>
using namespace std;

auto lambdaManip = [] (ostream& stream) -> ostream& {
    stream << "Hello world" << endl;
};
function<ostream& (ostream&)> functionManip = [] (ostream& stream) -> ostream& {
    stream << "Hello world" << endl;
};

int main (int argc, char** argv) {
    cout << lambdaManip;    // OK
    cout << functionManip;  // Compiler error
}

The second cout statement fails with the following:

g++-4 src/Solve.cpp -c -g -std=c++0x -o src/Solve.o -I/home/ekrohne/minisat
src/Solve.cpp: In function 'int main(int, char**)':
src/Solve.cpp:24:11: error: cannot bind 'std::ostream' lvalue to 'std::basic_ostream<char>&&'
/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/c++/ostream:579:5: error:   initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits<char>, _Tp = std::function<std::basic_ostream<char>&(std::basic_ostream<char>&)>]'

Why does this fail? I'm using cygwin gcc 4.5.3.

While I'm asking, I'm not nuts about using std::function everywhere, due to the efficiency problems. But I do wish to write functions that return lambda functions, and have no idea how to do so without std::function. For example, something like the following would be great

auto getAdditionFunctor();

auto getAdditionFunctor() {
    return [] (int x, int y) { return x + y };
};

...but obviously does not work. Is there an alternate syntax that does work? I can't imagine what it could be, so I may be stuck with std::function.

If I had a solution to the second question, then the first question would be moot.


Thank you.

Defining operator<<(ostream&, std::function<ostream&(ostream&)> helped. I had misread a web page, and was under the impression that ostream was smart enough to treat an arbitrary object that had an operator() as a manipulator. I was wrong about this. Furthermore, the simple lambda I built was probably just getting compiled into a plain old function, just as I was told. Indeed, if I use variable capture to ensure that the lambda is not a simple function, then the compiler fails. Also, objects with operator() defined are not (by default) treated as manipulators:

class Manipulator {
    ostream& operator()(ostream& stream) const {
        return stream << "Hello world" << endl;
    };
} classManip;

function<ostream& (ostream&)> functionManip = [] (ostream& stream) -> ostream& {
    return stream << "Hello world" << endl;
};

int main (int argc, char** argv) {
    const string str = "Hello world"; 
    auto lambdaManip = [&] (ostream& stream) -> ostream& {
        return stream << str << endl;     
    };

    cout << classManip;     // Compiler error
    cout << lambdaManip;    // Compiler error
    cout << functionManip;  // Compiler error
}

Further update: it turns out a slightly more robust solution than the ones below can be accomplished with:

// Tell ostreams to interpret std::function as a
// manipulator, wherever it sees one.
inline ostream& operator<<(
        ostream& stream, 
        const function<ostream& (ostream&)>& manipulator) {
    return manipulator( stream );
}

This code has an extra const. I discovered this trying to actually implement the solution in my project.

Scriptural answered 22/10, 2012 at 0:35 Comment(4)
Did you mean to leave out a 'return' in those manipulators?Meshwork
I had read that those are implied, and adding them doesn't help. In this case I think they're more readable with the returns, but they're legal without. After all, everything works works fine if I comment out cout << functionManip;.Scriptural
@EdKrohne : return is not optional here – your lambdas invoke UB by having a non-void return type and not returning a value, just as with any other function. §6.6.3/2: "Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function." Appearing to work fine is merely one possible manifestation of UB.Cowbind
@Cowbind Thank you. Again had misread a web site.Scriptural
M
7

If you look at operator<< for ostream, there's no overload for taking a std::function - and that's basically what you're trying to do here with cout << functionManip. To fix this, either define the overload yourself:

ostream& operator<<(ostream& os, std::function<ostream& (ostream&)>& s)
{
    return s(os);
} 

Or pass the stream as an argument to the function:

functionManip(std::cout);

As for why the lambda is working, given that the return type of a lambda is undefined, and there is an overload for using a function pointer:

ostream& operator<< (ostream& ( *pf )(ostream&));

The lambda is probably wrapping everything utilizing a a struct and defining operator() which in this case will work exactly like a function pointer. This to me is the most likely explanation, hopefully someone can correct me if I'm wrong.

Mchale answered 22/10, 2012 at 0:59 Comment(1)
The behavior isn't accidental, it's mandated by the Standard. A lambda that doesn't capture anything is always implicitly convertible to a plain old function pointer. (And inversely: a lambda that does capture anything is never implicitly convertible to a function pointer.)Zambrano
G
5

This isn't the cause of the error, but since you've defined both lambdaManip and functionManip as having the return type ostream& I believe you've forgotten to add return stream; to both.


The call cout << functionManip fails because there is no operator<<(ostream&, std::function<ostream&(ostream&)> defined. Add one and the call will succeed.

ostream& operator<<(ostream& stream, function<ostream& (ostream&)>& func) {
  return func( stream );
}

Alternatively, you can call functionManip as

functionManip( cout );

This will work without adding the operator<< definition.


As for your question about returning a lambda, since the lambda returned by getAdditionFunctor is a capture-less lambda, it can be implicitly converted to a function pointer.

typedef int(*addition_ptr)(int,int);
addition_ptr getAdditionFunctor()
{
  return [] (int x, int y) -> int { return x + y; };
}

auto adder = getAdditionFunctor();
adder(10,20); // Outputs 30
Greenland answered 22/10, 2012 at 1:0 Comment(2)
Good point, I should have thought about that for my toy example. But what if I'm capturing? Variable capture is the point of returning a lambda from a function; if I didn't want to do variable capture, I'd just use a plain old function in the first place.Scriptural
+1. I didn't know capture-less lambdas were implicitly convertible to function pointers, but it make perfect sense.Mchale
Z
5

The previous answers got the state of the art right, but if you are going to be heavily using std::functions and/or lambdas as stream manipulators in your code, then you might just want to add the following function template definition to your codebase, at global scope:

template<class M>
auto operator<< (std::ostream& os, const M& m) -> decltype(m(os))
{
    return m(os);
}

The trailing return type there uses expression SFINAE, so this particular operator<< overload won't even participate in overload resolution unless m(os) is a well-formed expression. (This is what you want.)

Then you can even do things like

template<class T>
auto commaize(const T& t)
{
    return [&t](std::ostream& os) -> std::ostream& {
        return os << t << ", ";
    };
}

int main()
{
    std::cout << commaize("hello") << commaize("darling") << std::endl;
}

(Notice the total lack of any std::function objects in the above code! Try to avoid stuffing lambdas into std::function objects unless you absolutely have to, because constructing a std::function object can be expensive; it can even involve memory allocation.)

It would make sense for C++17 to add this operator<< overload to the standard library, but I'm not aware of any concrete proposals in that area at the moment.

Zambrano answered 20/1, 2016 at 23:57 Comment(3)
I think I found the reason, why it is not in the standard... o.O (or I messed up, which is always also possible...). I placed the template operator<< right after #include <iostream> into a project-global header and from then on, it seemed to try take over all operator<<() functions in the project. (clang++-13, debian linux, -std=c++20). So I guess the "expression SFINAE" is not really working (anymore?!).Clarinda
@BitTickler: Expression SFINAE has issues in old versions of MSVC, but that's all. I suspect you've got a bug somewhere. If you post a godbolt.org link (like this but with your broken code), I could take a look.Zambrano
Sorry - cannot reproduce that in canonical example. I wanted to make progress in another direction and so I kicked this approach at first signs of trouble.Clarinda

© 2022 - 2024 — McMap. All rights reserved.