Именованное форматирование строки параметров в С++

Мне интересно, есть ли библиотека типа Boost Format, но которая поддерживает именованные параметры, а не позиционные. Это обычная идиома, например. Python, где у вас есть контекст для форматирования строк, который может или не может использовать все доступные аргументы, например

mouse_state = {}
mouse_state['button'] = 0
mouse_state['x'] = 50
mouse_state['y'] = 30

#...

"You clicked %(button)s at %(x)d,%(y)d." % mouse_state
"Targeting %(x)d, %(y)d." % mouse_state

Существуют ли библиотеки, предлагающие функциональность этих двух последних строк? Я ожидаю, что он предложит API что-то вроде:

PrintFMap(string format, map<string, string> args);

В Googling я нашел много библиотек, предлагающих варианты позиционных параметров, но ни один из них не поддерживает названные. В идеале библиотека имеет несколько зависимостей, поэтому я могу легко ее поместить в свой код. С++ не будет столь же идиоматичным для сбора именованных аргументов, но, вероятно, кто-то там подумал об этом больше, чем я.

Важное значение имеет производительность, в частности, я хочу, чтобы сокращения памяти (всегда сложные на С++), так как это может запускаться на устройствах без виртуальной памяти. Но даже медленный, чтобы начать с, вероятно, будет быстрее, чем писать его с нуля.

Ответ 1

Кажется, что ответ - нет, нет библиотеки С++, которая делает это, а программисты на С++, по-видимому, даже не видят необходимости в ней, основываясь на полученных комментариях. Мне придется снова написать свой собственный.

Ответ 2

библиотека fmt поддерживает именованные аргументы:

print("You clicked {button} at {x},{y}.",
      arg("button", "b1"), arg("x", 50), arg("y", 30));

И в качестве синтаксического сахара вы можете даже (ab) использовать пользовательские литералы для передачи аргументов:

print("You clicked {button} at {x},{y}.",
      "button"_a="b1", "x"_a=50, "y"_a=30);

Для краткости пространство имен fmt опущено в приведенных выше примерах.

Отказ от ответственности: я являюсь автором этой библиотеки.

Ответ 3

Я всегда критиковал С++ I/O (особенно форматирование), потому что, на мой взгляд, это шаг назад в отношении C. Форматы должны быть динамичными и, в частности, имеют смысл для загрузки их из внешнего ресурса в виде файла или параметра.

Я никогда не пробовал, прежде чем на самом деле реализовать альтернативу, и ваш вопрос заставил меня попытаться инвестировать некоторые выходные в эту идею.

Уверенная проблема была более сложной, чем я думал (например, просто целочисленная процедура форматирования - 200+ строк), но я думаю, что этот подход (строки динамического формата) более полезен.

Вы можете загрузить мой эксперимент из эту ссылку (это только файл .h) и тестовую программу из эта ссылка (тест, вероятно, не правильный термин, я использовал его, чтобы увидеть, удалось ли мне скомпилировать).

Ниже приведен пример

#include "format.h"
#include <iostream>

using format::FormatString;
using format::FormatDict;

int main()
{
    std::cout << FormatString("The answer is %{x}") % FormatDict()("x", 42);
    return 0;
}

Он отличается от подхода boost.format, потому что использует именованные параметры и потому что слова формата и формат словаря должны быть построены отдельно (и для пример прошел). Также я считаю, что параметры форматирования должны быть частью string (например, printf), а не в коде.

FormatDict использует трюк для разумного соблюдения синтаксиса:

FormatDict fd;
fd("x", 12)
  ("y", 3.141592654)
  ("z", "A string");

FormatString вместо этого просто анализируется с помощью const std::string& (я решил подготовить строки форматирования, но более медленный, но, вероятно, приемлемый подход будет просто передавать строку и каждый раз переписывать ее).

Форматирование может быть расширено для пользовательских типов, специализируясь на шаблоне функции преобразования; например

struct P2d
{
    int x, y;
    P2d(int x, int y)
        : x(x), y(y)
    {
    }
};

namespace format {
    template<>
    std::string toString<P2d>(const P2d& p, const std::string& parms)
    {
        return FormatString("P2d(%{x}; %{y})") % FormatDict()
            ("x", p.x)
            ("y", p.y);
    }
}

после этого экземпляр P2d можно просто поместить в словарь форматирования.

Также возможно передать параметры функции форматирования, разместив их между % и {.

В настоящее время я только реализовал целочисленную специализацию форматирования, которая поддерживает

  • Фиксированный размер с выравниванием влево/вправо/в центре
  • Пользовательское заполнение char
  • Общая база (2-36), нижняя или верхняя.
  • разделитель цифр (с пользовательским char и счетчиком)
  • Переполнение char
  • Отображение знака

Я также добавил несколько ярлыков для обычных случаев, например

"%08x{hexdata}"

- шестнадцатеричное число с 8 цифрами, заполненными "0".

"%026/2,8:{bindata}"

- 24-битное двоичное число (как требуется "/2") с разделителем цифр ":" каждые 8 ​​бит (как требуется ",8:").

Обратите внимание, что код - это всего лишь идея, и на данный момент я просто предотвратил копии, когда, вероятно, разумно разрешить хранить как форматированные строки, так и словари (для словарей важно, однако, дать возможность избежать копирования объекта только потому, что его нужно добавить к FormatDict, а в то время как IMO это возможно, это также то, что поднимает нетривиальные проблемы о временах жизни).

UPDATE

Я сделал несколько изменений в первоначальном подходе:

  • Теперь можно скопировать строки формата
  • Форматирование для пользовательских типов выполняется с использованием классов шаблонов вместо функций (это позволяет частичную специализацию)
  • Я добавил форматтер для последовательностей (два итератора). Синтаксис все еще сырой.

Я создал проект github для него, с ускорением лицензирования.

Ответ 4

Хорошо, я также добавлю свой собственный ответ, но не знаю, что я знаю (или закодировал) такую ​​библиотеку, но чтобы ответить на бит "сохранить разряд памяти".

Как всегда, я могу представить себе компромисс между скоростью и памятью.

С одной стороны, вы можете разобрать "Just In Time":

class Formater:
  def __init__(self, format): self._string = format

  def compute(self):
    for k,v in context:
      while self.__contains(k):
        left, variable, right = self.__extract(k)
        self._string = left + self.__replace(variable, v) + right

Таким образом, вы не держите "проанализированную" структуру под рукой, и, надеюсь, большую часть времени вы просто вставляете новые данные на место (в отличие от Python, строки С++ не являются неизменяемыми).

Однако это далеко не эффективно...

С другой стороны, вы можете построить полностью построенное дерево, представляющее проанализированный формат. У вас будет несколько классов типа: Constant, String, Integer, Real и т.д. И, возможно, некоторые подклассы/декораторы, а также для самого форматирования.

Я думаю, однако, что самый эффективный подход состоял бы в том, чтобы иметь какое-то сочетание двух.

  • взорвать строку формата в список Constant, Variable
  • индексируйте переменные в другой структуре (хэш-таблица с открытой адресацией будет делать красиво или что-то похожее на Loki::AssocVector).

Вот вы: вы закончили с двумя динамически распределенными массивами (в основном). Если вы хотите, чтобы один и тот же ключ повторялся несколько раз, просто используйте std::vector<size_t> в качестве значения индекса: хорошие реализации не должны выделять какую-либо память динамически для векторов малого размера (VС++ 2010 не менее 16 байт ценность данных).

При оценке самого контекста найдите экземпляры. Затем вы разбираете форматировщик "как раз вовремя", проверяете его как текущий тип значения, с которым его можно заменить, и обрабатывайте формат.

Плюсы и минусы:  - Just In Time: вы снова и снова просматриваете строку  - One Parse: требуется много выделенных классов, возможно, много распределений, но формат проверяется на входе. Как и Boost, его можно использовать повторно.  - Смешивание: более эффективно, особенно если вы не заменяете некоторые значения (допускаете какое-то "нулевое" значение), но отсрочка разбора формата задерживает сообщение об ошибках.

Лично я пошел бы за схемой One Parse, пытаясь сохранить распределения с помощью boost::variant и шаблона стратегии как можно больше.

Ответ 5

Учитывая, что Python сам написан на C, и что форматирование является такой часто используемой функцией, вы можете (игнорируя проблемы с копированием) разорвать соответствующий код из интерпретатора python и портировать его для использования STL-карт, а не Питоны native dicts.

Ответ 6

Я написал библиотеку для этого кукольника, проверьте его на GitHub.

Взносы - хорошие результаты.