Я пытаюсь разработать более приятный интерфейс C ++ для библиотеки C, которая отправляет древовидные выражения по каналу связи (а-ля iostreams vs stdio). Я не уверен, возможно ли вообще создать DSL в C ++ для обозначения этих деревьев избегая при этом времени выполнения, и если да, то как.
Существует библиотека C, которая может отправлять «выражения» по каналу связи. «Выражение» здесь означает древовидную структуру, которую можно удобно записать способом, аналогичным вызовам функций.
Например,
f(1, 2, g(3), "foo")
обозначает это дерево:
Некоторые из вас могут узнать Mathematica на данный момент, но я решил оставить это, поскольку это не имеет отношения к вопросу.
Мы ссылаемся на f
как голова а также 1
, 2
, g(3)
как аргументы.
Чтобы отправить это выражение, мы напишем следующее:
putHead("f" /* name */, 3 /* argument count */);
putInteger(1);
putInteger(2);
putHead("g" /* name */, 1 /* argument count */);
putInteger(3);
putString("foo");
Можно ли разработать для этого более удобный C ++ API со следующими функциями?
iostreams
против stdio
)f(1,2,g(3))
сверху) из которого автоматически выводится число аргументовЯ могу сделать (1) с потоковым интерфейсом (то есть нет необходимости явно указывать тип Integer, String и т. Д. Каждого аргумента). Я могу сделать (2) таким образом, что требует дополнительных вычислений во время выполнения. Но я не знаю, если (2) / (3) все вместе возможны с учетом особенностей C ++. В конечном итоге я хотел бы иметь удобную запись для этих выражений в самом C ++.
Так можно ли создать DSL в C ++ для этого избегая всех накладных расходов во время выполнения? Если да, то как? Я не обязательно ищу полное решение с кодом в качестве ответа, просто несколько указателей для начала или краткое изложение подхода, который, вероятно, сработает.
Я могу придумать несколько способов представления дерева в C ++. Реализация некоторых из них (# 1, # 2) будет включать построение промежуточной структуры, а затем вызов функции C в конце. Другие (# 5, # 6) могут вызывать функции C по мере необходимости, потому что число дочерних элементов любого узла известно во время компиляции.
1) Использование свободного интерфейса и перегрузки:
Tree()
.head("f")
.put(1)
.put(2)
.head("g")
.put(3)
.end()
.put("foo")
.end();
2) Или, альтернативно, без вызовов end ():
head("f")
.put(1)
.put(2)
.put(head("g")
.put(3))
.put("foo");
3) Использование оператора<<:
Tree("f")
<< 1
<< 2
<< (Tree("g") << 3)
<< "foo";
4) Использование оператора ():
Tree("f")
(1)
(2)
(Tree("g")(3))
("foo")
5) Использование std :: initializer_list, где Node — это тип, который неявно конвертируется из int и std :: string:
Tree { "f", {
1,
2,
{ "g", { 3 } }
"foo",
} }
6) Использование вариадических шаблонов:
head("f").put(
1,
2,
head("g").put(3)
"foo");
Других решений пока нет …