Форматирование именованных параметров в C ++ - PullRequest
16 голосов
/ 11 сентября 2010

Мне интересно, есть ли библиотека типа 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 я нашел много библиотек, предлагающих варианты позиционных параметров, но ни одной, которые поддерживают именованные.В идеале у библиотеки мало зависимостей, поэтому я легко могу вставить ее в свой код.C ++ не будет столь же идиоматичным для сбора именованных аргументов, но, возможно, кто-то там думал об этом больше, чем я.

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

Ответы [ 6 ]

6 голосов
/ 11 июня 2015

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

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

или, если у вас есть локальные переменные с такими же именами:

const char *button = b1;
int x = 50, y = 30;
print("You clicked {button} at {x},{y}.", FMT_CAPTURE(button, x, y));

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

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

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

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

6 голосов
/ 03 апреля 2011

Я всегда критиковал ввод-вывод C ++ (особенно форматирование), потому что, на мой взгляд, это шаг назад по отношению к 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, поскольку использует именованные параметры и потомуиспользовать строку формата и словарь формата предназначены для создания отдельно (и, например, для передачи).Также я думаю, что параметры форматирования должны быть частью строки (например, 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 может быть просто помещен в словарь форматирования.

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

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

  1. Фиксированный размер с выравниванием по левому / правому краю / центру
  2. Пользовательский символ заполнения
  3. Общая база (2-36), нижний или верхний регистр
  4. Разделитель цифр (с пользовательским символом и счетом)
  5. Переполнение символа
  6. Отображение знака

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

"%08x{hexdata}"

- это шестнадцатеричное число с 8 цифрами, дополненное нулями.

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

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

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

ОБНОВЛЕНИЕ

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

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

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

2 голосов
/ 12 сентября 2010

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

1 голос
/ 12 сентября 2010

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

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

С одной стороны, вы можете разобрать "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, строки C ++ не являются неизменяемыми).

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

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

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

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

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

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

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

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

0 голосов
/ 08 июля 2016

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

Вклады приветствуются.

0 голосов
/ 02 апреля 2011

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

...