Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

{{ bustache }}

C++20 implementation of {{ mustache }}, compliant with spec v1.1.3.


  • fmt (or C++20 <format>)

Optional Dependencies

Supported Features

  • Variables
  • Sections
  • Inverted Sections
  • Comments
  • Partials
  • Set Delimiter
  • Lambdas
  • HTML escaping (configurable)
  • Template inheritance (extension)

Other Features

  • Customizable behavior on unresolved variable
  • Trait-based user-defined model
  • Variable format string, e.g. {{var:*^10}}.
  • List expansion section, e.g. {{*map}}({{key}} -> {{value}}){{/map}}.
  • Filter section, e.g. {{?filter}}...{{/filter}}.


{{ mustache }} is a template language for text-replacing. When it comes to formatting, there are 2 essential things -- Format and Data. {{ mustache }} also allows an extra lookup-context for Partials. In {{ bustache }}, we represent the Format as a bustache::format object, and Data and Partials can be anything that implements the required traits.

Quick Example

bustache::format format{"{{mustache}} templating"};
std::unordered_map<std::string, std::string> data{{"mustache", "bustache"}};
std::cout << format(data); // should print "bustache templating"


Data Model

{{ bustache }} doesn't required a fixed set of predefined data types to be used as data model. Instead, any type can be used as data model. Most STL-compatible containers will work out-of-the-box, including the ones that you defined yourself!


#include <bustache/model.hpp>

Model Traits

To meet the Model concept, you have to implement the traits:

struct bustache::impl_model<T>
    static constexpr model kind;

where model can be one of the following:

  • model::atom
  • model::object
  • model::list
// Required by model::atom.
struct bustache::impl_test<T>
    static bool test(T const& self);

// Required by model::atom.
struct bustache::impl_print<T>
    static void print(T const& self, output_handler os, char const* fmt);

// Required by model::object.
struct bustache::impl_object<T>
    static void get(T const& self, std::string const& key, value_handler visit);

// Required by model::list.
struct bustache::impl_list<T>
    static bool empty(T const& self);
    static void iterate(T const& self, value_handler visit);

See udt.cpp for more examples.

Compatible Trait

Some types cannot be categorized into a single model (e.g. variant), to make it compatible, you can implement the trait:

struct bustache::impl_compatible<T>
    static value_ptr get_value_ptr(T const& self);

See model.hpp for example.

Format Object

bustache::format parses in-memory string into AST.


#include <bustache/format.hpp>



explicit format(std::string_view source); // [1]
format(std::string_view source, bool copytext); // [2]
format(ast::document doc, bool copytext); // [3]
  • Version 1 doesn't hold the text, you must ensure the source is valid and not modified during its use.
  • Version 2~3, if copytext == true the text will be copied into the internal buffer.


A manipulator combines the format & data and allows you to specify some options.

template<class T>
manipulator</*unspecified*/> format::operator()(T const& data) const;

// Specify the context for partials.
template<class T>
manipulator</*unspecified*/> manipulator::context(T const&) const noexcept;

// Specify the escape action.
template<class T>
manipulator</*unspecified*/> manipulator::escape(T const&) const noexcept;

Render API

render can be used for customized output.


#include <bustache/render.hpp>

template<class Sink, class Escape = no_escape_t>
void render
    Sink const& os, format const& fmt, value_ref data,
    context_handler context = no_context_t{}, Escape escape = {},
    unresolved_handler f = nullptr

Context Handler

The context for partials can be any callable that meets the signature:

(std::string const& key) -> format const*;

Unresolved Handler

The unresolved handler can be any callable that meets the signature:

(std::string const& key) -> value_ptr;

Sink (Output Handler)

The sink can be any callable that meets the signature:

(char const* data, std::size_t count) -> void;

Escape Action

The escape action can be any callable that meets the signature:

template<class OldSink>
(OldSink const& sink) -> NewSink;

There're 2 predefined actions: no_escape (default) and escape_html, if no_escape is chosen, there's no difference between {{Tag}} and {{{Tag}}}, the text won't be escaped in both cases.

Stream-based Output

Output directly to the std::basic_ostream.


#include <bustache/render/ostream.hpp>


template<class CharT, class Traits, class Escape = no_escape_t>
void render_ostream
    std::basic_ostream<CharT, Traits>& out, format const& fmt,
    value_ref data, context_handler context = no_context_t{},
    Escape escape = {}, unresolved_handler f = nullptr

template<class CharT, class Traits, class... Opts>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& out, manipulator<Opts...> const& manip)


// Create format from source.
bustache::format format(...);
// Create the data we want to output.
my_data data{...};
// Create the context for Partials.
my_context context{...};
// Output the result.
std::cout << format(data).context(context).escape(bustache::escape_html);

String Output

Generate a std::string.


#include <bustache/render/string.hpp>


template<class String, class Escape = no_escape_t>
void render_string
    String& out, format const& fmt,
    value_ref data, context_handler context = no_context_t{},
    Escape escape = {}, unresolved_handler f = nullptr

template<class... Opts>
std::string to_string(manipulator<Opts...> const& manip);


bustache::format format(...);
std::string txt = to_string(format(data).context(context).escape(bustache::escape_html));

Advanced Topics


The lambdas in {{ bustache }} accept signatures below:

  • (ast::view const* view) -> format
  • (ast::view const* view) -> Value

A ast::view is a parsed list of AST nodes, you can make a new ast::view out of the old one and give it to a format. Note that view will be null if the lambda is used as variable.

Error Handling

The constructor of bustache::format may throw bustache::format_error if the parsing fails.

class format_error : public std::runtime_error
    explicit format_error(error_type err, std::ptrdiff_t position);

    error_type code() const noexcept;
    std::ptrdiff_t position() const noexcept;

error_type has these values:

  • error_set_delim
  • error_baddelim
  • error_delim
  • error_section
  • error_badkey

You can also use what() for a descriptive text.


Compare with 2 other libs - mstch and Kainjow.Mustache. See benchmark.cpp.

Sample run (VS2019 16.7.6, boost 1.73.0, 64-bit release build):

Running F:\code\bus\x64\Release\bus.exe
Run on (8 X 3600 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x8)
  L1 Instruction 32 KiB (x8)
  L2 Unified 256 KiB (x8)
  L3 Unified 12288 KiB (x1)
Benchmark               Time             CPU   Iterations
bustache_usage       4675 ns         4708 ns       149333
mstch_usage         80919 ns        81961 ns         8960
kainjow_usage       23993 ns        24065 ns        29867

Lower is better.



Copyright (c) 2014-2021 Jamboree

Distributed under the Boost Software License, Version 1.0. (See accompanying
file LICENSE_1_0.txt or copy at