c42f / tinyformat

Minimal, type safe printf replacement library for C++
537 stars 75 forks source link

Ability to format sequentially #21

Open gabyx opened 10 years ago

gabyx commented 10 years ago

Would it be possible to have the following feature:

auto op = tfm::formatOp("%i , %i "); op % a; op % b; op % c ; // this would result in an assert or exception

Thanks for considering this :-), this makes sequential parsing in values and converting them into a string extremely easy :-)

c42f commented 10 years ago

Can you give some real world code where you need to do this? I'd like to better understand exactly what you're trying to do (for example, do you want this because you've got a conditional branch when adding arguments? Is it because you want to pass op through to a function? Is it some other reason?).

The tfm::vformat() machinery basically lets you do this already. For example, if you define the class VFormatList as below, you can pass it through to tfm::vformat.

#include <tinyformat.h>

#include <vector>
#include <memory>

class VFormatList
{
    public:
        // Attempt to avoid copying and storing the value where possible.
#if 0
        template<typename T>
        void add(const T& value)
        {
            m_argList.emplace_back(value);
        }

        template<typename T>
        void add(const T&& value)
        {
            m_argStore.emplace_back(new AnyT<T>(std::move(value)) );
            const T& storedValue = static_cast<AnyT<T>&>(*m_argStore.back()).value;
            m_argList.emplace_back(storedValue);
        }
#endif
        // Edit - actually the above will break for the user in many weird and wonderful ways.
        // It's probably just a bad idea!  The following should work, though it's quite inefficient.
        template<typename T>
        void add(const T& value)
        {
            m_argStore.push_back(std::unique_ptr<AnyT<T>>(new AnyT<T>(value)));
            const T& storedValue = static_cast<AnyT<T>&>(*m_argStore.back()).value;
            m_argList.emplace_back(storedValue);
        }

        operator tfm::FormatList()
        {
            return tfm::FormatList(m_argList.data(), m_argList.size());
        }

    private:
        struct Any { };

        template<typename T>
        struct AnyT : Any
        {
            T value;
            AnyT(const T& value) : value(value) { }
        };

        std::vector<tfm::detail::FormatArg> m_argList;
        std::vector<std::unique_ptr<Any>> m_argStore;
};

int main()
{
    VFormatList args;
    std::string str = "asdf";

    args.add(str);
    args.add(42.42);

    tfm::vformat(std::cout, "%s : %.1f\n", args);
    return 0;
}
gabyx commented 10 years ago

Thanks for the help, I have exactly the mentioned case when I want to add arguments in a loop at runtime. Thanks for adding this example, is that already explained in the doku?

By the way: template void add(const T&& value)

is an universal reference (it could be also a lvalue reference, but then the first overload would be taken right?)

BR Gabriel

p.S: You are also the one who maintains Aqsis ? :-) . I am the same guy who asked about the Sphere and RIB files :-) last weeks :-) Thanks for the help!

c42f commented 10 years ago

The reason I'm asking for a "real world" case, is that I'd write your example as:

tfm::format("%i , %i ", a, b);

and I'm not sure how having an incremental interface could make that shorter or clearer.

Regarding loops, a format string is already basically limited to a fixed number of arguments, so I don't see how it's natural to use one in a loop. If you have some array a and you want to format in a loop, why not do something like:

double a[10] = {/* some example array */};
std::ostringstream out;
for (int i = 0; i < 10; ++i)
    tfm::format(out, "%.6f ", a[i]);
std::string s = out.str();

The reason I played the trick with the rvalue reference add(const T&& value) is that you definitely need to copy any rvalue (placing in m_argStore) so that the temporary object isn't deallocated before the call to vformat. On the other hand, I was trying to avoid the copy if it's an lvalue, especially since some types don't even have a copy constructor. In hindsight this is going to break for the user in weird and wonderful ways, especially for local variables inside a loop.

About aqsis - yes that's me, though I don't really count as a maintainer anymore due to being too time poor. I still read the mailing list, happy to provide the occasional bit of help.

gabyx commented 10 years ago

Ok, maybe you need some more information:

I use tinyformat for an StringFormattingNode excution node.

this execution node has input sockets and output sockets the ouput socket is a std::string the format string and the inputs sockets can be specified with an XML, so the XML is read at runtime and the StringFormattingNode is created with the inputs sockets specified, e.g. one double, one int , one std::string and a format string "%d : %i : %s". In this context, when executing the node, it retrieves all inputs and outputs the formatted string So this is done in a loop with m_vformatList.add( input[i]->getValue() ) and then the list is converted with the help of your example :-). I cannot use the variadic argument tfm::format() function because the arguments can only be accessed in a loop.

thats the whole context,

c42f commented 10 years ago

Ok thanks, that explanation makes a lot more sense. I think it's a relatively unusual use case, but certainly a valid thing to want to do. Can you guarantee that the format arguments have a lifetime outside the scope of your loop? In that case you can remove the Any wrapper class stuff I have above and it should be pretty efficient.

It's also technically possible to have a format iterator which would sort out the issues with format argument lifetime in a more elegant way. In fact, the internals in tinyformat version 1.3 had a class detail::FormatIterator. This wasn't part of the public API and I've since removed it in the name of implementation simplicity, but usage would have looked something like

std::ostringstream out;
tfm::detail::FormatIterator fmt(out, "%i , %i ");
fmt.accept(a);
fmt.accept(b);
fmt.finish();
std::string s = out.str();
gabyx commented 10 years ago

Ahh jeah that would be cool as well :-) I dont think this will overbloat the simplicity as it already is :-), could you still include that part of functionality in an additional include header?

thanks a lot :-)

by the way, a really cool library!

c42f commented 10 years ago

I can't put back the FormatIterator without some significant internal work, at least not in a general way which would avoid the inelegant use of something like to Any as in VFormatList above.

I'm open to the idea of having a separate header or headers containing useful examples which people can modify for their own needs.

alfishe commented 6 years ago

Probably it's simpler to explain use-case by example in C# where this paradigm is used extensively. https://msdn.microsoft.com/en-us/library/system.string.format(v=vs.110).aspx

Benefits - you can reuse indexed placeholders and bound values multiple times without duplication in parameters. But it's not printf-like style anymore and probably not "tiny"format model. It's like creating new "power"format component

c42f commented 6 years ago

@alfishe did you see #45 ? I'm going to merge that as soon as I've got time to properly look at it again.