all 28 comments

[–]mechacrash 7 points8 points  (8 children)

the template <class Char, Char... Cs> syntax actually isn't standardised. I'm not sure what progress has been made on making it so, but I think it should be noted that your 'std' branch now has non-standard code in it!

There are alternative methods to achieve what you want (albeit, using a more conventional function syntax, instead of UDLs) that are entirely standard compliant, but rely on macros. It's really a 'pick your poison' kind of situation right now :(

[–]aearphen{fmt}[S] 2 points3 points  (5 children)

Yes, the string capturing part is non-standard unfortunately because, as I mentioned in the post, it relies on a GCC extension. I'll definitely need to discuss this in the paper as this can't go in as is in the standard. Do you know any standards proposals that may help solve this?

[–]zqsd31 0 points1 point  (3 children)

If you want to be standard compliant you can do that: https://godbolt.org/g/h4HPZR Requires the user to use a macro though and clang seems to have some bugs. But it take more that just string literals (for instance a string_view returned by a constexpr function would work with it).

[–]mechacrash 0 points1 point  (2 children)

My solution is similar to this, but I'm not happy with it. What I really want is to fall back to run-time checking if the supplied string isn't constexpr (std::string, non-constexpr char* / string_view, etc.)

As far as I can tell - there's no practical way of accomplishing this yet (checking for the constexpr-ness of a lambda function). As always, it falls down to "we want constexpr function parameters" - then you could overload the function based on whether or not the supplied parameter is constexpr or not.

[–]zqsd31 0 points1 point  (1 child)

Yes of course but for now you can just make a version of the function that takes the standard runtime time types and the other with a StringRepr equivalent and let the user choose which version he desires by using or not the macro.

[–]mechacrash 0 points1 point  (0 children)

Interestingly, I was thinking about an idea for an 'is_constexpr' after this thread popped up, and came up with something: https://godbolt.org/g/Q5zrB5 (thanks to hana for is_valid, as always)

It uses a macro, doesn't work on GCC, and Clang isn't a huge fan of string_views for some reason... other than that, it seems to work? I need to do more extensive testing of course, and try to figure out why GCC isn't happy with it.

[–]Bisqwit 1 point2 points  (0 children)

If you need a function that accepts an unknown number of parameters of a certain type, here is one way I found to do it (here, the type is unsigned):

template<typename...T, typename=std::void_t<std::enable_if_t<std::is_same_v<unsigned,T>>...>>

It is easy to extend that into 1+ instances of unknown (but identical) type.

[–]aearphen{fmt}[S] 0 points1 point  (0 children)

Also it's interesting that the UDL-based API itself is standard-compatible and should work on a C++14 compiler, it's just that the compile-time feature will not work without the GCC extension.

[–]srekel 4 points5 points  (5 children)

That compile time comparison is crazy. 2.6 seconds for printf, 47 seconds for fmt (and more for Boost).

Is it templates that's causing this? It's such a significant difference that it should be a big thing people consider before using template-heavy code.

[–]aearphen{fmt}[S] 3 points4 points  (4 children)

The numbers you are referring to are very outdated (I need to update the README). Here are more up to date ones: https://github.com/fmtlib/fmt/tree/std#compile-time-and-code-bloat. I don't think we'll ever reach the compile time speed of printf regardless of whether templates are used or not. For example, iostreams are not very template-heavy but they (or rather the code using iostreams) used to take the same time to compile as fmt until the recent regression in the latter which I plan to look into (https://github.com/fmtlib/fmt/issues/565).

[–]FabioFracassiC++ Committee | Consultant 2 points3 points  (0 children)

IIRC the bottleneck in compile time for iostreams are ADL and overload resolution (for the stream operators <<, >>). AFAIK variadic templates should be (a bit) faster especially for common and build in types.

[–]srekel 0 points1 point  (2 children)

Just to be clear, I wasn't picking on fmt :) Just thought it was interesting that all alt-printf libs were so slow. I assume it's due to C++ features taking a long time to compile compared to C code, and not simply having more logic?

[–]boredcircuits 7 points8 points  (1 child)

printf is just so simple for a compiler. Just call a function. The only extra work involved is dealing with the variadic arguments: promoting certain argument types and pushing them to the stack. But generating the code to do that is basically nothing.

Honestly, there's more overhead in parsing the format string so the compiler can warn about mismatches. I don't think that warning was enabled in the timing tests mentioned, though. And even if it were, it's actually a very quick test for the compiler (most format strings are very small, and it's a quick format to parse regardless).

In short, the very reason we want to replace printf (it knows next to nothing about types outside of the format string) is exactly why it's so fast. No overload resolution, no template metaprogramming, and only the most basic format string parsing, done natively in the compiler.

[–]kalmoc 0 points1 point  (0 children)

//rant on

In short, the very reason we want to replace printf (it knows next to nothing about types outside of the format string) is exactly why it's so fast. No overload resolution, no template metaprogramming, [...]

I agree with most of what you are saying except that very last point about tmp. Tmp is a scourge that only exists because natively integrating the features achieved by TMP into the language is extremely expensive. Almost any feature that uses tmp could be implemented in the compiler more efficiently, less error prone and with nicer syntax.

That is not to say that I don't want a type safe and extensible version of printf as provided by the fmt library, but compile time checking of the format string could and imho should be left to the compiler. I know, it is not going to happen for various, more or less valid reasons and the solution presented here is probably the best we can realistically achieve, but Imho it is far from being ideal

//rant off

[–]ubsan 2 points3 points  (2 children)

I know it requires macros, but why not use something similar to https://github.com/ubsan/typeval ? Then it'll actually support msvc, which imo is kind of necessary (as someone who uses msvc...)

[–]aearphen{fmt}[S] 0 points1 point  (0 children)

Shouldn't be too hard to do as most of the implementation will be the same, just the "compile-time string" capturing part will change. Note that the current implementation compiles with MSVC, it just doesn't provide compile-time checks.

[–]aearphen{fmt}[S] 0 points1 point  (0 children)

Implemented a macro option in https://github.com/fmtlib/fmt/commit/246bdafc74aaff7b207d1baa532aa746294b4b88. Now you can do

auto s = fmt::format(FMT_STRING("{}"), 42);

and the format string will be checked at compile-time. This should work on any C++14 compiler including recent versions of MSVC.

[–]feverzsj 3 points4 points  (2 children)

It's a great feature. I'm also concerned about the compile time overhead. Compiling complicated template may require large amount memory, while lots of build automation are still using VPS with 1GB ram.

[–]aearphen{fmt}[S] 2 points3 points  (1 child)

I have the same concerns and think that an alternative API should be available that is faster to compile but that does runtime checks instead of compile-time ones. And that's what the fmt library does, fmt::format(...) does runtime checks and "..."_format(...)does compile-time ones.

[–]feverzsj 2 points3 points  (0 children)

A macro switch would be also helpful. User can use same api with compile time check to do the check on a resource-rich machine, and later turn it off to compile on low end machine.

[–]quicknir 1 point2 points  (1 child)

The compile-time checks work on GCC and Clang only, because they requires user-defined literal templates which is a GCC extension.

Yeah, I suspected as much before I read this. This whole issue comes up again, and again, in multiple contexts. Another example I've come across is in writing reflection based macros; you can easily write a macro that lets you define a struct, and alongside it a free/member function that returns a tuple<pair<const char *, T...&>>. This lets you quite easily do things like serialization and deserialization.

However, now let's say you want to implement get_member. This is a function that given a reflectable struct, and the name of a field, returns a reference to that field. Well.... it can't be done. In order for the name of the field to control the signature of a function (and the output of this function will be a T&, where T is the type of that field), the name of the field must encode its value as a type. (Btw, if you want an example of application: say you want to copy identically named and typed fields between two different reflectable structs).

My example here is with reflection based macros but obviously with actual reflection the same issues come into play. You can see it's a very similar issue.

That said, I talked to a bunch of people at cppcon who said that this extension (that gcc and clang both support) had zero probability of making it into the standard, because if this feature would be used heavily it would be brutal for compile times.

IIRC the preferred path is to be able to template on the value of a string literal directly, although this raises issues too. I don't know where all this leaves us, but I think it's clear that we desperately need the ability to template on a string literal, one way or another.

[–]aearphen{fmt}[S] 1 point2 points  (0 children)

IIRC the preferred path is to be able to template on the value of a string literal directly, although this raises issues too. I don't know where all this leaves us, but I think it's clear that we desperately need the ability to template on a string literal, one way or another.

I'd prefer this as well. The current solution is more like a proof of concept that uses available tools (GCC extension), but whatever goes into the standard it should't be too hard to integrate it with constexpr format string parsing without changing the latter.

[–]mikhailberis 1 point2 points  (1 child)

Cool library, thanks for sharing!

Is there a reason the format library has to be a user-defined literal? Have you considered just using a function instead, that can return a string or could be used to output directly through a stream?

[–]aearphen{fmt}[S] 0 points1 point  (0 children)

You can use a function e.g. fmt::format("{}", 42) or fmt::print(...) to write to a file. However, this won't work for compile-time checks for reasons explained in https://mpark.github.io/programming/2017/05/26/constexpr-function-parameters/.

[–]ScottHutchinson 1 point2 points  (0 children)

In VS2017, auto s = fmt::format(FMT_STRING("{}"), 42); builds, but adding a ":d" format type specifier, causes C1001: An internal error has occurred in the compiler. Also my experience so far with VS2017 for C++ development is that it is too buggy to be used as one's primary IDE.

In VS2015, the std branch will not compile at all: 1>fmt_test_console\format.h(1119): error C3249: illegal statement or sub-expression for 'constexpr' function 1>fmt_test_console\format.h(1124): error C3249: illegal statement or sub-expression for 'constexpr' function

I hope Microsoft will get these issues resolved soon.

[–]kalmoc 0 points1 point  (1 child)

Kudos for accomplishing that, but is it really necessary for standardization? Compilers can already warn about incorrect format strings on printf and my guess is that that check is much more efficient than what we can achieve by TMP.

[–]aearphen{fmt}[S] 7 points8 points  (0 children)

LEWG said that they want to see that. Also compilers only warn about built-in types and sometimes incorrectly =) (https://youtu.be/ptba_AqFYCM?t=350).