Is there a way to omit the empty string literals (""
) in the argument list of the fmt::format
function?
I have the below snippet which gives the desired output:
#include <string>
#include <fmt/core.h>
int main( )
{
const std::string application_layer_text_head { fmt::format( "{:*<5}[Application Layer]{:*<51}\n\n", "", "" ) };
fmt::print( "{}", application_layer_text_head );
}
Output:
*****[Application Layer]***************************************************
So instead of writing this: fmt::format( "{:*<5}[Application Layer]{:*<51}\n\n", "", "" )
can we remove the empty literals and write this: fmt::format( "{:*<5}[Application Layer]{:*<51}\n\n" )
? I tried it but it failed to compile. Those two literals don't really serve any purpose so I want to find a way to not write them.
Just to clarify, I only want to have 5 *
in the beginning and then [Application Layer]
and then 51 *
and then 2 \n
.
As noted already, format
can't do this. But your worries about string concatenation being expensive are misplaced; repeated application of operator+
is expensive (performs new allocations, copies all existing data and new data, discards old data, over and over), but in-place concatenation with operator+=
and append
is cheap, especially if you pre-reserve
(so you're allocating once up-front and populating, not relying on amortized growth patterns in reallocation to save you). Even without pre-reserve
, std::string
follows amortized growth patterns, so repeated in-place concatenation is amortized O(1)
(per character added), not O(n)
in the size of the data so far.
The following should be, essentially by definition, at least as fast as formatting, though at the expense of a larger number of lines of code to perform the pre-reserve
to prevent reallocation:
#include <string>
#include <string_view>
#include <fmt/core.h>
using namespace std::literals;
int main( )
{
// Make empty string, and reserve enough space for final form
auto application_layer_text_head = std::string();
application_layer_text_head.reserve(5 + "[Application Layer]"sv.size() + 51 + "\n\n"sv.size());
// append is in-place, returning reference to original string, so it can be chained
// Using string_view literals to avoid need for any temporary runtime allocated strings,
// while still allowing append to use known length concatenation to save scanning for NUL
application_layer_text_head.append(5, '*').append("[Application Layer]"sv).append(51, '*').append("\n\n"sv);
fmt::print("{}", application_layer_text_head);
}
If you were okay with some of the concatenations potentially performing reallocation, and a final move construction to move the resources from the temporary reference to a real string, it simplifies to a one-liner:
const auto application_layer_text_head = std::move(std::string(5, '*').append("[Application Layer]"sv).append(51, '*').append("\n\n"sv));
or, given that 5 asterisks is short enough to type, the even shorter/simpler version:
const auto application_layer_text_head = std::move("*****[Application Layer]"s.append(51, '*').append("\n\n"sv));
But keeping it to a two-liner avoids the move construction and is a little safer:
auto application_layer_text_head = "*****[Application Layer]"s;
application_layer_text_head.append(51, '*').append("\n\n"sv);
Yeah, none of those are quite as pretty as a single format literal, even with "ugly" empty placeholders. If you prefer the look of the format string, just pass along the empty placeholders the way you're already doing.