5

In short

I am trying to design a nicer C++ interface for a C library that sends tree-like expressions through a communication channel (à la iostreams vs stdio). I am not sure if it is possible at all to design a DSL within C++ for notating these trees while avoiding runtime overhead, and if yes, how.

Simplified explanation of the C library

There is a C library which can send "expressions" through a communication channel. An "expression" here means a tree structure that can be conveniently notated in a manner similar to function calls.

For example,

f(1, 2, g(3), "foo")

denotes this tree:

Mathematica graphics

Some of you may recognize Mathematica at this point, but I decided to leave that out as it is irrelevant to the question.

We refer to f as head and 1, 2, g(3) as arguments.

To send this expression, we would write the following:

putHead("f" /* name */, 3 /* argument count */);
  putInteger(1);
  putInteger(2);
  putHead("g" /* name */, 1 /* argument count */);
    putInteger(3);
  putString("foo");

Question

Is it possible to design a more convenient C++ API for this with the following features?

  1. More concise to write (think iostreams vs stdio)
  2. Does away with the need to explicitly specify the argument count for each head; instead uses a convenient notation (analogous to f(1,2,g(3)) from above) from which it infers the argument count automatically
  3. Achieves (2) without runtime overhead (i.e. infer argument count at compile time)
  4. Must use the above described C interface behind the scenes

I can do (1) with a streams-like interface (i.e. no need to explicitly specify the type of Integer, String, etc. of each argument). I can do (2) in a way that involves extra runtime computation. But I do not know if (2)/(3) together are possible given the features of C++. Ultimately I would like to have a convenient notation for these expressions within C++ itself.

So is it possible to design a DSL within C++ for this while avoiding all runtime overhead? If yes, how? I am not necessarily looking for a full solution with code as the answer, just some pointers to get started, or a summary of an approach that would likely work.

Szabolcs
  • 24,728
  • 9
  • 85
  • 174
  • "expression templates" could be helpful – milleniumbug Mar 13 '16 at 13:30
  • 1
    Downvoters: please add a comment, and give me a chance to address any problems. – Szabolcs Mar 13 '16 at 15:49
  • Here are [three attempts](http://coliru.stacked-crooked.com/a/db8d49a5a958ccf7) to get close to the syntax you want. All three just stuff the arguments into a tuple, but it should be possible to recurse through those and make the appropriate C API calls. – melak47 Mar 14 '16 at 14:04
  • @melak47 Thank you so much for the hints. I was swamped with work so I couldn't give this enough time yet, but I wanted to give you a sign that I did look at your code and I appreciate it. – Szabolcs Mar 16 '16 at 08:44

1 Answers1

0

I can think of a few ways to represent a tree in C++. The implementation for some of these (#1, #2) would involve building an intermediate structure, and then calling the C functions at the end. Others (#5, #6) could call the C functions as you go, because the number of children of any node is known at compile-time.

1) Using a fluent interface and overloading:

Tree()
  .head("f")
    .put(1)
    .put(2)
    .head("g")
      .put(3)
      .end()
    .put("foo")
    .end();

2) Or alternatively, without calls to end():

head("f")
  .put(1)
  .put(2)
  .put(head("g")
    .put(3))
  .put("foo");

3) Using operator<<:

Tree("f")
  << 1
  << 2
  << (Tree("g") << 3)
  << "foo";

4) Using operator():

Tree("f")
  (1)
  (2)
  (Tree("g")(3))
  ("foo")

5) Using std::initializer_list where Node is a type that is implicitly convertible from both int and std::string:

Tree { "f", {
  1,
  2,
  { "g", { 3 } }
  "foo",
} }

6) Using variadic templates:

head("f").put(
  1,
  2,
  head("g").put(3)
  "foo");
Paul Brannan
  • 1,625
  • 20
  • 20