tinyformat icon indicating copy to clipboard operation
tinyformat copied to clipboard

Ability to format sequentially

Open gabyx opened this issue 10 years ago • 9 comments

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 :-)

gabyx avatar Oct 29 '14 20:10 gabyx

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;
}

c42f avatar Oct 30 '14 10:10 c42f

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<typename T> 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!

gabyx avatar Oct 30 '14 11:10 gabyx

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.

c42f avatar Oct 30 '14 14:10 c42f

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,

gabyx avatar Oct 30 '14 14:10 gabyx

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();

c42f avatar Oct 31 '14 13:10 c42f

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!

gabyx avatar Oct 31 '14 13:10 gabyx

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.

c42f avatar Oct 31 '14 13:10 c42f

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

alfishe avatar Dec 28 '17 16:12 alfishe

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

c42f avatar Dec 30 '17 01:12 c42f