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.