Есть ли способ опустить пустые строковые литералы ("") в списке аргументов функции fmt::format?
У меня есть приведенный ниже фрагмент, который дает желаемый результат:
#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 );
}
Выход:
*****[Application Layer]***************************************************
Итак, вместо того, чтобы писать это: fmt::format( "{:*<5}[Application Layer]{:*<51}\n\n", "", "" ), можем ли мы удалить пустые литералы и написать это: fmt::format( "{:*<5}[Application Layer]{:*<51}\n\n" )? Я пробовал, но не удалось скомпилировать. Эти два литерала на самом деле не служат никакой цели, поэтому я хочу найти способ не писать их.
Просто чтобы уточнить, я хочу иметь только 5 * в начале, затем [Application Layer], затем 51 *, а затем 2 \n.
Это более эффективно, чем operator+, но operator+= и append позволяют избежать большинства этих затрат (перераспределение на месте, а не создание новых string и отбрасывание старых, и reserve можно использовать для обеспечения единого распределения, если вам это действительно нужно).
Я думаю, что вопрос здесь действительно заключается в том, «Как мне отформатировать эту строку с 5 * слева и 51 * справа?», А не «как опустить пустые аргументы?» Или даже в более общем плане: как лучше всего отформатировать символ c n раз?
Разметка форматирования предназначена для форматирования строки с некоторой частью данных, предоставленных пользователем. Особенности специализированного синтаксиса в рамках форматирования могут регулировать работу этого форматирования, даже вставку символов и тому подобное. Но эта функциональность должна быть дополнением к основному действию: взять какой-либо предоставленный пользователем объект и внедрить его в строку.
Так что нет, у format нет механизма, который позволил бы вам избежать предоставления данных, предоставленных пользователями, которые и являются основной причиной существования format.
Также следует отметить, что само значение текста после : в спецификаторе формата определяется исходя из типа форматируемого объекта. «*<5» означает «выровнять по 5 символам, используя символы «*» для заполнения пробелов» только потому, что вы предоставили строку для этого конкретного параметра формата. Так что format не только не дает возможности сделать это, но и функционально не может. Вы должны указать, какой тип используется, потому что это неотъемлемая часть обработки значения "*<5".
Так есть ли другой лучший (более чистый) способ сделать это? Я выбрал формат, потому что он эффективен.
@digito_evo, вы можете создать собственный тип и написать для него собственные правила форматирования.
Как уже отмечалось, format не может этого сделать. Но ваши опасения по поводу дороговизны конкатенации строк неуместны; повторное применение operator+ дорого (выполняет новые распределения, копирует все существующие данные и новые данные, отбрасывает старые данные снова и снова), но конкатенация на месте с operator+= и append дешева, особенно если вы предварительно reserve (так что вы перераспределение сразу и заполнение, не полагаясь на модели амортизированного роста при перераспределении, чтобы спасти вас). Даже без предварительной reserve std::string следует амортизированным моделям роста, поэтому повторная конкатенация на месте амортизируется O(1) (на каждый добавленный символ), а не O(n) в размере данных до сих пор.
Следующее должно быть, по сути, по определению, по крайней мере таким же быстрым, как форматирование, хотя и за счет большего количества строк кода для выполнения предварительной reserve для предотвращения перераспределения:
#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);
}
Если вас устраивали некоторые конкатенации, потенциально выполняющие перераспределение, и конструкция окончательного перемещения для перемещения ресурсов из временной ссылки в реальную строку, это упрощается до однострочного:
const auto application_layer_text_head = std::move(std::string(5, '*').append("[Application Layer]"sv).append(51, '*').append("\n\n"sv));
Или, учитывая, что 5 звездочек достаточно для ввода, еще более короткая/простая версия:
const auto application_layer_text_head = std::move("*****[Application Layer]"s.append(51, '*').append("\n\n"sv));
Но сохранение его двухстрочным позволяет избежать конструкции перемещения и немного безопаснее:
auto application_layer_text_head = "*****[Application Layer]"s;
application_layer_text_head.append(51, '*').append("\n\n"sv);
Да, ни один из них не так красив, как литерал одного формата, даже с «уродливыми» пустыми заполнителями. Если вы предпочитаете внешний вид строки формата, просто передайте пустые заполнители так, как вы это уже делаете.
@Barry: Хм ... Вероятно, вы правы, поскольку он возвращается по ссылке, а не по значению; Я предполагаю, что std::move можно использовать, по крайней мере, для того, чтобы сделать его конструкцией перемещения, и хотя я не могу представить, почему вы объявляете его ссылкой напрямую, это будет проблемой, если вы попытаетесь встроить его в вызов метода, который его получил. по (висящей) ссылке. Так что да, наверное, лучше всего в две строки.
Спасибо за предложение. И если говорить о висячих ссылках, это действительно происходит в const auto application_layer_text_head = std::string(5, '*').append("[Application Layer]"sv).? Разве он не собирается делать конструкцию перемещения, потому что объект с правой стороны является временным (rvalue)?
@digito_evo Правая часть - это не rvalue, это lvalue.
@Барри Но почему? Это значение без какого-либо идентификатора. Как это может быть lvalue?
@digito_evo Потому что идентификаторы - это не единственные вещи, которые являются lvalue. string::append() возвращает ссылку lvalue.
@digito_evo: Причина, по которой он ведет себя таким образом, заключается в том, что он изменяет str на месте, чтобы избежать копирования, а возвращаемое значение всегда является ссылкой l-значения на исходный str. Это позволяет связывать вызовы методов без накладных расходов и не требует переназначения, когда у объекта уже есть имя, но это означает, что даже когда вы изначально работаете с r-значением, он все равно возвращает ссылку на l-значение. Они могли бы добавить версии append с указанием r-значения, но они не сделали этого с тех пор, как эта функция была добавлена в C++ 11. Такова жизнь.
@ Никол Болас Я хочу не только 5 *. Посмотрите на выводимый текст. Это то, что я хочу. И fmt::format хорошо работает в этом случае и намного эффективнее, чем operator+ из std::string. Так что есть смысл его использовать.