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 tagiostream
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 forStl
iostream
.
source to share
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)...};
}
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
.
source to share
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.
source to share
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).