Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a lambda for ostream?

I thought the call of the operator<< would generate a two-parameter function call. So, why does this not compile, then?

#include <iostream> // ostream
#include <iomanip> // setw, setfill
using std::ostream; using std::setw; using std::setfill;
struct Clock {
    int h_, m_, s_;
    Clock(int hours, int minutes, int seconds)
    : h_{hours}, m_{minutes}, s_{seconds} {}
    void setClock(int hours, int minutes, int seconds) {
        h_ = hours; m_ = minutes; s_ = seconds;
    }
    friend ostream& operator<<(ostream&os, const Clock& c) {
        auto w2 = [](ostream&os, int f) -> ostream& {
            return os << setw(2) << setfill( '0' ) << f; };
        return os << w2(c.h_) <<':'<<w2(c.m_)<<':'<<w2(c.s_); // ERROR
    }
};

The error is (gcc-6)

$ g++-6 -std=gnu++1y ...
file.cpp: In function ‘std::ostream& operator<<(std::ostream&, const Clock&)’:
file.cpp:745:33: error: no match for call to ‘(operator<<(std::ostream&, const Clock&)::<lambda(std::ostream&, int)>) (const int&)’
         return os << w2(c.h_) <<':'<<w2(c.m_)<<':'<<w2(c.s_);
                             ^

I also tried the call os << w2(os,c.h_) but gcc and I agreed that was nonsense. Also I tried the lambda as auto as possible:

auto w2 = [](auto&os, auto f) {
    return os << setw(2) << setfill( '0' ) << f; };

also no luck.

Any hints?

like image 580
towi Avatar asked Apr 13 '26 18:04

towi


1 Answers

I thought the call of the operator<< would generate a two-parameter function call.

No, invoking an overloaded operator<< is basically the same as invoking a binary function:

a << b;
// is equivalent to
operator<<(a, b);
// or to
a.operator<<(b);

What you're trying to do is invoke operator<< using a lambda that returns an ostream& as the right-hand argument, but you're not passing the ostream& argument to the lambda itself.


os << w2(os,c.h_) is syntactically valid, but will not compile because there is no operator<<(ostream&, ostream&) definition.

What you can do is simply invoke the lambda without streaming it:

friend ostream& operator<<(ostream&os, const Clock& c) {
    auto w2 = [](ostream&os, int f) -> ostream& {
        return os << setw(2) << setfill( '0' ) << f; };

    w2(os, c.h_); 
    os <<':'; 
    w2(os, c.m_); 
    os << ':'; 
    return w2(os, c.s_);
}

wandbox example


If you want to achieve your desired syntax, you will need a little more work. Here's a possible solution:

template <typename TF>
struct streamable : TF
{
    streamable(TF&& f) : TF{std::move(f)} { } 
};

template <typename TF>
auto& operator<<(ostream& os, const streamable<TF>& s)
{
    s(os); return os;
}

template <typename TF>
auto make_streamable_impl(TF f)
{
    return streamable<TF>(std::move(f));
}

template <typename TF>
auto make_streamable(TF f)
{
    return [&](auto&& x) mutable
    { 
        return make_streamable_impl([&](ostream& os) -> auto&
        { 
            f(os, x); 
            return os; 
        });
    };
}

Usage:

friend ostream& operator<<(ostream&os, const Clock& c) {
    auto w2 = make_streamable([](ostream&os, int f) -> ostream& {
        return os << setw(2) << setfill( '0' ) << f; });
    return os << w2(c.h_) <<':'<<w2(c.m_)<<':'<<w2(c.s_);
}

wandbox example

Note that a real implementation should probably perfectly-capture the arguments into the lambdas.

like image 110
Vittorio Romeo Avatar answered Apr 15 '26 06:04

Vittorio Romeo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!