1

I have written a template file which is as follows

Hello ${Name}
I like ${food}

I wanted to write a c++ code which generates the following code using the template file as reference

Hello John
I like Pasta
I like Pasta
I like Pasta

Is there a way to do this in C++? I came across "ctemplate", but I was not convinced. The application I am developing is cross-platform. ( I wanted to do something like string template in c#)

Aditya Bhat
  • 95
  • 2
  • 10

2 Answers2

2

I've written a template expansion 'engine' using Boost Spirit before:

It's really versatile

  • supports nested expansions
  • supports recursive expansions
  • supports dynamic expansions (e.g. if you want a variable to be expanded with a different value depending on the context)

I've just adapted it to your question's macro syntax. See it Live On Coliru


Update

Okay, since performance appears to be the primary goal, here's a highly optimized expansion engine, in a benchmark:

#include <string>
#include <sstream>
#include <map>
#include <boost/utility/string_ref.hpp>

template <typename Range>
std::string expand(Range const& key)
{
    if (key == "Name")
        return "John";
    if (key == "food")
        return "Pasta";
    return "??";
}

#include <iostream>
int main()
{
    static const std::string msg_template = 
        "Hello ${Name}\n"
        "I like ${food}\n"
        ;

    std::ostringstream builder;
    builder.str().reserve(1024); // reserve ample room, not crucial since we reuse it anyways

    for (size_t iterations = 1ul << 22; iterations; --iterations)
    {
        builder.str("");
        std::ostreambuf_iterator<char> out(builder);

        for(auto f(msg_template.begin()), l(msg_template.end()); f != l;)
        {
            switch(*f)
            {
                case '$' : 
                    {
                        if (++f==l || *f!='{')
                        {
                            *out++ = '$';
                            break;
                        }
                        else
                        {
                            auto s = ++f;
                            size_t n = 0;

                            while (f!=l && *f != '}')
                                ++f, ++n;

                            // key is [s,f] now
                            builder << expand(boost::string_ref(&*s, n));

                            if (f!=l)
                                ++f; // skip '}'
                        }
                    }
                default:
                    *out++ = *f++;
            }
        }
        // to make it slow, uncomment:
        // std::cout << builder.str();
    }
    std::cout << builder.str();
}

It runs 2^22 (4,194,304) iterations in ~0.775s

See it Live On Coliru too (where it runs in ~1.8s).

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • See here for a similar task, solved without Boost Spirit, too; http://stackoverflow.com/a/17128601/85371 – sehe May 07 '14 at 15:35
  • Added a streaming solution without boost that's bleedingly fast. – sehe May 07 '14 at 15:53
0

The standard libraries have excellent facilities for everyday regex parsing (which is what you need), take a look at the docs here.

You need to learn about regex if you've never heard of it - this is at least one place outlining the details.

Alternately if you are concerned with performance and your task is literally as simple as you describe then writing your own parser should be very straight forward using two streams and seeking forward for the ${ escape sequence while copying across to the output stream substituting as needed.

Elemental
  • 7,365
  • 2
  • 28
  • 33
  • There will be a performance issue with regex. Say I have to write "I like Pasta" a million times, it will be an issue. – Aditya Bhat May 07 '14 at 11:01
  • @AdityaBhat My solution works completely in streaming mode (except for the value of the macro itself, although that could be arranged as well) – sehe May 07 '14 at 15:21