Closed TorstenRobitzki closed 5 years ago
Well, there is this ancient school of philosophy that claims: "nothing must break encapsulation and decoupling" ...
pprint(" Hey Ma, Look: { %foo } !", _foo_instance ) ;
They go as far as claiming above "integration" naturally breaks both in one go, for the foo
type... I might be sure they would advise on this course of action:
pprint("Hey, Ma, look: { }!", foo::to_string( _foo_instance ) ) ;
Complete decoupling and complete encapsulation ... Silly people ...
Well, this requires some kind of std::string
. Being in the embedded world, providing a statically allocated buffer, that can be used for multiple invocations of to_string
could take the buffer and "consume" portion of it by taking it by reference. Something like this:
char buffer[100];
auto buf = create_consumable( buffer );
pprint("Multiple Foos: {} / {}", to_string( buf, foo1 ), to_string( buf, foo2 ) );
Which of cause won't work as both calls to to_string()
must have a side effect on buf, but the order of evaluating would be undefined.
Thanks for your suggestion.
and of cause, it requires some kind of support for std::string
in pprint
.
Encapsulation and decoupling. That is a fundamental postulate. Not a "suggestion".
There is a rumor some of them ancients have developed ancient parts of std::
twilight zone.
Your line of thinking is not that uncommon. Whan I aksed that question, in particular std::exception
has been brought to my meek persona attention, so I can learn by example:
// two instances of some offsprings of std::exception
// x1 and x2, came in
pprintp("Look Ma, no integration and no std::strings ! {s} {s} ", x1.what(), x2.what() ) ;
They actully use const char *
... Silly people ...
@TorstenRobitzki i am glad that the language turns out to be useful in your project! I keep seeing new issue items very late because for some reason i don't get emails about it.
I think the cleanest way to extend this is this:
MyType
provide some nonstandard %FOO
format implementation in your printf
implementation that handles the formatting of the variable's content in the output. This part would happen without any change to pprintpp
yet.namespace pprintpp {
template <> struct type2fmt<MyType> { using type = char_tl_t<'F', 'O', 'O'>; };
}
as seen between the other types here.
Is this something that might work for you?
pprintpp
is simply a format string preprocessor that creates no runtime code. Thinking about different models might be possible, but i guess we would need kind of rethink the whole thing from the beginning (which would be fun of course). This is why i suggest this approach with separated concerns.
Yes, we are fully aware, that pprintpp "just" turns a typelist and a format string in a new format, but we though, that we are probably not the only and first one, that have the requirements to use user defined types. As we are basically using snprintf()
and not printf()
, we wrapped snprintf()
already to have some formatting like this:
char buffer[ 100 ];
const std::size_t buffer_used = format( buffer, "{s} = {}\r\n", "Elf", 11 );
I though about your approach already, but rejected the idea, because it inverses the usual library dependencies (in addition, we have printf() in conan package that would then be under constant change).
I'm currently thinking of wrapping buffer
in some kind of type to select a different overload of snprintf()
and to change pprintf()
to check if unsupported types (aka user defined) type support a specific overloaded function, that could be used to pass the buffer and the object to, to fill the buffer.
I also try to understand, if pprintpp
would still be the right place to start with or if it might be easier to start from scratch.
@TorstenRobitzki first I hope you do not mind my approach before. :)
Your project has requirements which we do not know but, I really do not see what might be wrong with:
// just a sketch
struct Foo {
whatever data_ ;
const char * to_string () const {
// 1. somehow put data_ string presentation to this->buffy_
// 2. return the native pointer to it
return this->buffy_ ;
};
private:
char buffy_[BUFSIZ]{char(0)};
} ;
// ... somewhere else
void test ( Foo const & foo_ ) {
pprintf(" Foo instance: { s } \n\n", foo_.to_string() ) ;
}
pprintf
is simple, effective and feasible as it is ... Perhaps we should think of it as we think of stdio.h
printf()
: it is there it is useful and it is unchangeable :)
Your project has requirements which we do not know but, I really do not see what might be wrong with:
Well, having a buffer inside every data type that has to have a string representation is simply overkill and it won't work for enumerations and it requires support by the user defined type, which might be unsuitable for types from existing libraries.
It seems like it would be quite helping in your case if your printf-implementation would recognize "callable" arguments. It could then call them when they are needed and they serialize themselves into a buffer (and they all could use the same one), or something like that. But that is on a layer that is completely off from where pprintpp works... I see that it's tempting to do something in pprintpp, but it's hard to see where this would really fit when thinking this through.
@TorstenRobitzki , The point is in the last sentence :
pprintf
is simple, effective and feasible as it is ... Perhaps we should think of it, as we think of stdio.h printf() : it is there it is useful and it is unchangeable :)
Yes ... "callable arguments" is one OK way of doing it. It does not change pprintf
... There are many C++ variadic arguments based designs, working that way, for example...
Thanks for the open discussion. I will think about this a little bit longer, but I think the solution to our requirements are somewhere outside of pprintfpp.
@TorstenRobitzki You might consider this schema
#include "../pprintpp.hpp"
#include <cassert>
#include <cstdio>
#include <memory>
using namespace std;
/*
somewhat inspired with
https://msdn.microsoft.com/en-us/magazine/dn913181.aspx
*/
template <typename T>
inline T frm_arg(T value) noexcept
{
return value;
}
template<typename ... Args>
inline auto to_buff
(char const* fmt, Args ...args)
noexcept -> unique_ptr<char[]>
{
assert(fmt != nullptr);
// 1: what is the size required
size_t size = 1 + std::snprintf(nullptr, 0, fmt, frm_arg(args) ...);
assert(size > 0);
// 2: use it at runtime
auto buf = std::make_unique<char[]>(size + 1);
size = std::snprintf(buf.get(), size, fmt, frm_arg(args) ...);
assert(size > 0);
return buf;
}
///////////////////////////////////////////////////////////////////////
struct Foo {
const char* name = "Foo";
};
inline constexpr char const * frm_arg(Foo const & foo_ ) noexcept
{
return foo_.name;
}
///////////////////////////////////////////////////////////////////////
extern "C" int test_4(int, wchar_t* [])
{
auto rez{ to_buff(
"\n\nFoo: %s, Foo: %s, %s %d\n\n", Foo{"A"}, Foo{"B"}, "The answer is:", 42
)};
pprintf("{s}", rez.get() );
return EXIT_SUCCESS;
}
I wonder, if it would be possible to add support for user defined types and if so, how. Or if not, where would one add support for user defined types. Ideas?
Could we somehow extend pprintpp to have
print()
invoked with some statically allocated buffer for formatting purpose?I know, pprintpp() is "just" creating printf style format strings, but in real world applications, user defined types have to be added sooner or later.