Home > OS >  How to omit the empty arguments in fmt::format?
How to omit the empty arguments in fmt::format?

Time:01-11

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.

CodePudding user response:

Formatting markup is meant for formatting a string with some piece of user-provided data. The particulars of the specialized syntax within formatting can adjust how this formatting works, even inserting characters and the like. But this functionality is meant to be a supplement to the basic act: taking some user-provided object and injecting it into a string.

So no, format doesn't have a mechanism to allow you to avoid providing the user-provided data that is the entire reason format exists in the first place.

It should also be noted that the very meaning of the text after the : in a format specifier is defined based on the type of the object being formatted. The "*<5" means "align to 5 characters using '*' characters to fill in the blanks" only because you provided a string for that particular format parameter. So not only does format not provide a way to do this, it functionally cannot. You have to tell it what type is being used because this is an integral part of processing what "*<5" means.

CodePudding user response:

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, it simplifies to a one-liner:

const auto application_layer_text_head = 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 = "*****[Application Layer]"s.append(51, '*').append("\n\n"sv);

Yeah, it's not quite as nice as a single format literal with placeholders. If you like them, just pass along the empty placeholders the way you're already doing.

  • Related