How can I call a member function via a stream insert statement?

Using gcc 4.8 with C ++ 11 enabled, I have a class like this:

class OutStream {
public:
    OutStream& operator<<(const char* s);
    OutStream& operator<<(int n);
    OutStream& operator<<(unsigned int n);
    // ...
    OutStream& vformat(const char* fmt, __VALIST args);
    OutStream& format(const char* fmt, ...);
};

      

When I use this class by calling operators directly, it works as I expected:

OutStream out;
out.operator<<(1).format(" formatted %04X ", 2).operator<<("3\n");

      

Output:

1 formatted 0002 3

      

Now I would like to get the same output, but with a stream recording <<

, maybe like this:

OutStream out;
out << 1 << format(" formatted %04X ", 2) << "3\n";

      

Of course it didn't compile because there was no such operator to stream my method OutStream.format()

.

There may be a solution where format()

is a free function that returns a string, but that must first write all the output format()

to the buffer. I need a solution without std::string

or otherwise using heap or buffer - at best, a solution that produces almost the same code as when calling operators directly.

Any suggestions?

Edit, 2014-10-20:

  • For a better understanding of my requirements: I am on plaintext embedded development using gcc-arm-embedded gcc cross toolchain.
  • I need to apply the solution for some embedded target systems (most of them are Cortex-M0 / M3 / M4). Some of them have very limited resources (Ram and Flash) and some of my target systems need to work without using the heap.
  • For some reason I don't use Stl

    iostream

    . However, the tag iostream

    was set by editing seh; I would keep it due to thematic matching and the solution I found for my problem might also be applicable for Stl

    iostream

    .
+3


source to share


4 answers


Using C ++ 14 index_sequence (There are a million different implementations on SO ):

template <typename...Ts>
class formatter {
    const char* fmt_;
    std::tuple<Ts...> args_;

    template <std::size_t...Is>
    void expand(OutStream& os, std::index_sequence<Is...>) && {
        os.format(fmt_, std::get<Is>(std::move(args_))...);
    }

public:
    template <typename...Args>
    formatter(const char* fmt, Args&&...args) :
        fmt_{fmt}, args_{std::forward<Args>(args)...} {}

    friend OutStream& operator << (OutStream& os, formatter&& f) {
        std::move(f).expand(os, std::index_sequence_for<Ts...>{});
        return os;
    }
};

template <typename...Args>
formatter<Args&&...> format(const char* fmt, Args&&...args) {
    return {fmt, std::forward<Args>(args)...};
}

      

DEMO

The compiler should easily insert the operation formatter

and delete the temporary object. Indeed, this function:



void test_foo() {
    OutStream out;
    out << 1 << format(" formatted %04X ", 2) << "3\n";
}

      

results in build (g ++ 4.9.0 -std = c ++ 1y -O3-targeting x64) :

.LC0:
    .string " formatted %04X "
.LC1:
    .string "3\n"
test_foo():
    pushq   %rbx
    movl    $1, %esi
    subq    $16, %rsp
    leaq    15(%rsp), %rdi
    call    OutStream::operator<<(int)
    movl    $2, %edx
    movl    $.LC0, %esi
    movq    %rax, %rbx
    movq    %rax, %rdi
    xorl    %eax, %eax
    call    OutStream::format(char const*, ...)
    movq    %rbx, %rdi
    movl    $.LC1, %esi
    call    OutStream::operator<<(char const*)
    addq    $16, %rsp
    popq    %rbx
    ret

      

therefore everything is configured correctly; there is no trace in the resulting code formatter

.

+5


source


There are three extension points for the class std::basic_ostream

and its operator<<

that look relevant here:

  • "Insert" function that takes and returns std::ios_base&

    .
  • "Insert" function that takes and returns std::basic_ios<C, T>&

    .
  • "Insert" function that takes and returns std::basic_ostream&

    .

It is unfortunate that all three work with function pointers rather than instances std::function

, making it difficult to supply a closure. In your case, you want to specify a format string - and possibly format arguments - a la std::setw()

.



You can find a discussion of how to implement these manipulators in Cay Horstmann's well-prepared essay Extending the iostream Library . In particular, see Section 3, Manipulators, for how you can return an object from your function format()

that serves as a closure, and write a function operator<<()

for that object.

This will lead to some extra copying if you want to grab temporary values ​​in your closure and you might find it difficult to get the list of variational arguments. Start with a simple interface (take just one argument perhaps), make sure it writes to the target stream and builds from there.

+3


source


Try the following:

OutStream out;
(out << 1).format(" formatted %04X ", 2) << "3\n";

      

+2


source


Consider using GNU autosprintf . This is very small. No, really . It is essentially a wrapper around vasprintf

. All autosprintf needs are the implementation std::string

and your usual standalone C headers. Here is the header and documentation . An example of how you could use it:

OutStream out;
out << 1 << gnu::autosprintf(" formatted %04X ", 2) << "3\n";

      

(Actually, if you are using fixed-size strings, you can change this to not use it at all std::string

. Of course, there is still the assumption that you have implemented vasprintf

some form of heap allocation as well).

+1


source







All Articles