Samples for using LLVM and Clang as a library

My llvm-clang-samples repository has been public for over a year, and has become quite popular recently. I figured it's about time I write a quick blog post explaining how it came to be and what the principles behind it are.

One on the biggest selling points of LLVM and Clang is that they're packaged as libraries with a rich C++ API (and also C APIs), and thus can be easily embedded in larger applications. However, if you look online for samples of making this embedding happen, you'll start noticing two fairly big problems with most of the code you find:

  1. All official LLVM tutorials (and many of the samples online) talk about building your project inside the LLVM tree, using LLVM's own build system. Actually, LLVM has two official build systems (one based on autotools and another on CMake), so the samples will be further fragmented between these. While building within the LLVM tree if fine for experimenting, it won't work if you want to integrate LLVM as a library into a parent project.
  2. LLVM's and Clang's C++ API is changing constantly; C++ API stability is not a design goal of the LLVM community (one could argue that instability is a design goal). Therefore, if you find some code a few months after it was posted online, there's a very good chance that it won't compile or run. Code from a couple of years ago? Forget about it.

A few years ago, when I was getting started with LLVM, I was also frustrated by these problems. So I rolled my sleeves and banged out a simple Makefile that made it possible to build a few samples out of the LLVM tree, and then industriously kept it up to date with LLVM and Clang changes. I had it in my private code coffers for a while, but last year figured it could be useful to others, so I published it in a public Github repository.

The idea of llvm-clang-samples is very simple - it's just a bunch of self-contained programs using LLVM or Clang as libraries, centered around the Makefile, which dictates how to build these programs vs. a built version of LLVM & Clang itself. I chose a Makefile since it's the lowest common denominator of build systems - my Makefile is purposefully very simple and linear - think of it as a shell script with some automatic dependency management thrown in.

With simple configuration, this Makefile can build programs vs. either a built source checkout of LLVM, or released binaries (so compiling LLVM itself is not really required). There's also a suite of tests I run to make sure that the samples are not only built correctly, but also run correctly and keep producing expected results.

The samples themselves cover a wide range of LLVM & Clang uses. There are standalone programs using LLVM as a library to process LLVM IR. There's a sample of building a dynamically-linked pass that can be loaded as a plugin with opt. There are samples of Clang tooling, a Clang plugin, and so on.

How do I keep the repository up-to-date, though? There are two paths. First, every time there is a new official LLVM release (this happens about twice a year), I make sure the samples build and work fine with it, and create a new branch. Forever after, checking this branch out will give you the repository in a state that works with the relevant released version. This is very useful because for most users, the bleeding edge is not required and they can do just fine with the latest released version. Moreover, if there's a need to work with an even older release, the repository already has some history, going back to LLVM 3.3 (released in June 2013).

The master branch of the repository is kept in sync with LLVM by me manually, and the "last known good LLVM revision" it works against appears on the main README file. I usually try to refresh it every week or two. It's very rare for it to fall more than a few weeks behind. And of course, if you find it did fall behind, don't hesitate to open an issue (or better yet, create a pull request) - I usually get to these fairly quickly.

Perfect forwarding and universal references in C++

One of the new features in C++11 aimed at increased code efficiency is the emplace family of methods in containers. std::vector, for example, has an emplace_back method to parallel push_back, and emplace to parallel insert.

Here's a short demonstration of the benefits these new methods bring:

class MyKlass {
  MyKlass(int ii_, float ff_) {...}


some function {
  std::vector<MyKlass> v;

  v.push_back(MyKlass(2, 3.14f));
  v.emplace_back(2, 3.14f);

If you trace the execution of the constructors and destructor of MyKlass, you'll see something like the following for the push_back call:

  • Constructor for a temporary MyKlass object
  • Move constructor (if one was defined for MyKlass, otherwise a copy constructor) for the object actually allocated inside the vector
  • Destructor for the temporary

This is quite a lot of work. Much of it isn't required though, since the object passed to push_back is obviously an rvalue that ceases to exist after the statement is completed; there's no reason to create and destroy a temporary - why not just construct the object inside the vector directly?

This is exactly what emplace_back does. For the v.emplace_back(2, 3.14f) call above, all you see is a single constructor invocation. This is the object constructed inside the vector. No temporaries are needed.

emplace_back accomplishes this by invoking the constructor of MyKlass on its own and forwarding its arguments to the constructor. This feat is made possible by two new features in C++11: variadic templates and perfect forwarding. In this article I want to explain how perfect forwarding works and how to use it.

The perfect forwarding problem

Let func(E1, E2, ..., En) be an arbitrary function call with generic parameters E1, E2, ..., En. We'd like to write a function wrapper such that wrapper(E1, E2, ..., En) is equivalent to func(E1, E2, ..., En). In other words, we'd like to define a function with generic parameters that forwards its parameters perfectly to some other function.

To have something concrete to relate this definition to, think of the emplace_back method discussed above. vector<T>::emplace_back forwards its parameters to a constructor of T, without actually knowing how T looks like.

Next, I'm going to show a few examples of how we might approach this in pre-11 C++. For simplicity's sake, I'll put variadic templates aside; let's assume all we need to forward is two arguments.

The first approach that comes to mind is:

template <typename T1, typename T2>
void wrapper(T1 e1, T2 e2) {
    func(e1, e2);

This will obviously not work if func accepts its parameters by reference, since wrapper introduces a by-value passing step. If func modifies its by-reference parameter, it won't be visible in the caller of wrapper (only the copy created by wrapper itself will be affected).

OK, then, we can make wrapper accept its parameters by reference. This should not interfere with func's taking parameters by value, because the call to func within wrapper will create the required copy.

template <typename T1, typename T2>
void wrapper(T1& e1, T2& e2) {
    func(e1, e2);

This has another problem, though. Rvalues cannot be bound to function parameters that are references, so the following completely reasonable calls will now fail:

wrapper(42, 3.14f);                  // error: invalid initialization of
                                     //        non-const reference from
                                     //        an rvalue

wrapper(i, foo_returning_float());   // same error

And no, making those reference parameters const won't cut it either, because func may legitimately want to accept non-const reference parameters.

What remains is the brute-force approach taken by some libraries: define overloads for both const and non-const references:

template <typename T1, typename T2>
void wrapper(T1& e1, T2& e2)                { func(e1, e2); }

template <typename T1, typename T2>
void wrapper(const T1& e1, T2& e2)          { func(e1, e2); }

template <typename T1, typename T2>
void wrapper(T1& e1, const T2& e2)          { func(e1, e2); }

template <typename T1, typename T2>
void wrapper(const T1& e1, const T2& e2)    { func(e1, e2); }

Exponential explosion. You can imagine how much fun this becomes when we want to cover some reasonable amount of function parameters. To make things worse, C++11 adds rvalue references to the mix (which we'd also want to forward correctly), and this clearly isn't a scalable solution.

Reference collapsing and special type deduction for rvalues

To explain how C++11 solves the perfect forwarding problem, we have to first understand two new rules that were added to the language.

Reference collapsing is the easier one to explain, so let's start with it. Taking a reference to a reference is illegal in C++. However, it can sometimes arise in the context of templates and type deduction:

template <typename T>
void baz(T t) {
  T& k = t;

What happens if we call this function as follows:

int ii = 4;

In the template instantiation, T is explicitly set to int&. So what is the type of k inside? What the compiler "sees" is int& & - while this isn't something the user is allowed to write in code, the compiler simply infers a single reference from this. In fact, prior to C++11 this wasn't standardized, but many compilers accepted such code anyway because these cases occasionally arise in template metaprogramming. With the addition of rvalue references in C++11, it became important to define what happens when various reference types augment (e.g. what does int&& & mean?).

The result is the reference collapsing rule. The rule is very simple. & always wins. So & & is &, and so are && & and & &&. The only case where && emerges from collapsing is && &&. You can think of it as a logical-OR, with & being 1 and && being 0.

The other addition to C++11 relevant to this article is special type deduction rules for rvalue references in some cases [1]. Given a function template like:

template <class T>
void func(T&& t) {

Don't let T&& fool you here - t is not an rvalue reference [2]. When it appears in a type-deducing context, T&& acquires a special meaning. When func is instantiated, T depends on whether the argument passed to func is an lvalue or an rvalue. If it's an lvalue of type U, T is deduced to U&. If it's an rvalue, T is deduced to U:

func(4);            // 4 is an rvalue: T deduced to int

double d = 3.14;
func(d);            // d is an lvalue; T deduced to double&

float f() {...}
func(f());          // f() is an rvalue; T deduced to float

int bar(int i) {
  func(i);          // i is an lvalue; T deduced to int&

This rule may seem unusual and strange. That's because it is. However, it starts making sense when we realize it was designed to solve the perfect forwarding problem.

Solving perfect forwarding with std::forward

Let's get back to our original wrapper template. Here's how it should be written in C++11:

template <typename T1, typename T2>
void wrapper(T1&& e1, T2&& e2) {
    func(forward<T1>(e1), forward<T2>(e2));

And this is forward [3]:

template<class T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept {
  return static_cast<T&&>(t);

Let's say we call:

int ii ...;
float ff ...;
wrapper(ii, ff);

Examining the first argument (since the second is handled similarly): ii is an lvalue, so T1 is deduced to int& following the special deduction rules. We get the call func(forward<int&>(e1), ...). Therefore, forward is instantiated with int& and we get this version of it:

int& && forward(int& t) noexcept {
    return static_cast<int& &&>(t);

Now it's time to apply the reference collapsing rule:

int& forward(int& t) noexcept {
    return static_cast<int&>(t);

In other words, the argument is passed on by reference to func, as needed for lvalues.

The other case to handle is:

wrapper(42, 3.14f);

Here the arguments are rvalues, so T1 is deduced to int. We get the call func(forward<int>(e1), ...). Therefore, forward is instantiated with int and we get this version of it:

int&& forward(int& t) noexcept {
    return static_cast<int&&>(t);

The by-reference argument is casted to an rvalue reference, which is what we wanted from forward.

One can see forward as a pretty wrapper around static_cast<T&&>(t) when T can be deduced to either U& or U&&, depending on the kind of argument to the wrapper (lvalue or rvalue). Now we get wrapper as a single template that handles all kinds of forwarding cleanly.

The forward template exists in C++11, in the <utility> header, as std::forward.

Another thing I want to mention is the use of std::remove_reference<T>. In fact, it you think about it, forward could do without it. Reference collapsing does the job already, so std::remove_reference<T> is superfluous. It's there to turn the T& t into a non-deducing context (according to the C++ standard, section, thus forcing us to explicitly specify the template parameter when calling std::forward.

Universal references

In his talks, blog posts and book, Scott Myers gave the name "universal references" to rvalues that appear in a type-deducing context. Whether this is a useful mnemonic or not depends on the beholder; personally, when I first read the relevant chapters of the new "Effective C++", I found myself seriously confused on this topic. Only later, when I understood the underlying mechanisms (reference collapsing and special deduction rules), the topic became somewhat clearer.

The trap is that saying "universal references" [4] is surely more succinct and nice than "rvalue references in a type deducing context", but once you want to really understand some piece of code (rather than just cargo-culting boilerplate), you'll find that avoiding the full definition is impossible.

Examples of using perfect forwarding

Perfect forwarding is extremely useful, because it enables a kind of higher order programming. Higher order functions are functions that may take other functions as arguments or return them. Without perfect forwarding, higher order functions are cumbersome because there is no convenient way to forward arguments to wrapped functions. And by "functions" here I mean classes as well, whose constructors are still functions.

In the beginning of the article I mentioned the emplace_back method of containers. Another good examples is make_unique, which I described in the previous article:

template<typename T, typename... Args>
unique_ptr<T> make_unique(Args&&... args)
    return unique_ptr<T>(new T(std::forward<Args>(args)...));

There, I pleaded to ignore the strange && syntax and focus on the variadic template packs, but now there's no trouble fully understanding the code. It goes without saying that perfect forwarding and variadic templates very often go hand in hand, because we generally don't know how many arguments the functions or constructors we pass around accept.

For a significantly more complex use of perfect forwarding, you may also want to take a look at std::bind.

Variadic templates in C++

Prior to C++11, the only way to write functions that take an arbitrary number of arguments was to use variadic functions like printf, with the ellipsis syntax (...) and the accompanying va_ family of macros. If you've ever written code using this approach you know how cumbersome it is. In addition to being type unsafe (all type resolution has to be done explicitly with casts in va_arg, at runtime), it's also tricky to get right. The va_ macros perform low-level memory manipulation, and I've seen a lot of code that segfaults because it isn't using them carefully enough.

But what always bothered me most with this approach is leaving something that is clearly known at compile-time, to run-time. Yes, when we write a variadic function we don't know all the ways it's going to be used. But when the compiler puts the whole program together, it does know. It sees perfectly well all the invocations of the function throughout the program, and all the possible argument types it gets passed (types are, after all, resolved at compile-time in C++).

Variadic templates

One of the new features of C++11 is variadic templates. Finally, there's a way to write functions that take an arbitrary number of arguments in a type-safe way and have all the argument handling logic resolved at compile-time, rather than run-time. Variadic templates can be used for much more than just functions that take an arbitrary number of arguments; in this article I want to demonstrate some of these capabilities.

Basic example

Let's dive in, by implementing a function that adds all of its arguments together:

template<typename T>
T adder(T v) {
  return v;

template<typename T, typename... Args>
T adder(T first, Args... args) {
  return first + adder(args...);

And here are a couple of ways we could call it:

long sum = adder(1, 2, 3, 8, 7);

std::string s1 = "x", s2 = "aa", s3 = "bb", s4 = "yy";
std::string ssum = adder(s1, s2, s3, s4);

adder will accept any number of arguments, and will compile properly as long as it can apply the + operator to them. This checking is done by the compiler, at compile time. There's nothing magical about it - it follows C++'s usual template and overload resolution rules.

typename... Args is called a template parameter pack, and Args.. args is called a function parameter pack (Args is, of course, a completely arbitrary name and could be anything else). Variadic templates are written just the way you'd write recursive code - you need a base case (the adder(T v) declaration above) and a general case which "recurses" [1]. The recursion itself happens in the call adder(args...). Note how the general adder is defined - the first argument is peeled off the template parameter pack into type T (and accordingly, argument first). So with each call, the parameter pack gets shorter by one parameter. Eventually, the base case is encountered.

To get a better feel for the process, one can use the __PRETTY_FUNCTION__ macro [2]. If we insert the following as the first line in both versions of adder above:

std::cout << __PRETTY_FUNCTION__ << "\n";

And then execute adder(1, 2, 3, 8, 7), we'll see:

T adder(T, Args...) [T = int, Args = <int, int, int, int>]
T adder(T, Args...) [T = int, Args = <int, int, int>]
T adder(T, Args...) [T = int, Args = <int, int>]
T adder(T, Args...) [T = int, Args = <int>]
T adder(T) [T = int]

Some simple variations

When reading about C++ template meta-programming, one often hears about "pattern matching" and how this part of the language constitutes a fairly complete compile-time functional language.

The example shown above is very basic - template arguments are peeled off one by one until the base case is hit. Here's a somewhat more interesting display of pattern matching:

template<typename T>
bool pair_comparer(T a, T b) {
  // In real-world code, we wouldn't compare floating point values like
  // this. It would make sense to specialize this function for floating
  // point types to use approximate comparison.
  return a == b;

template<typename T, typename... Args>
bool pair_comparer(T a, T b, Args... args) {
  return a == b && pair_comparer(args...);

pair_comparer accepts any number of arguments and returns true if and only if they are pair-wise equal. The types are not enforced - everything that can be compared goes. For example:

pair_comparer(1.5, 1.5, 2, 2, 6, 6)

Returns true. But if we change the second argument to just 1, this won't compile since a double and int are not the same type.

More interestingly, pair_comparer will only work for an even number of arguments because they are peeled off in pairs and the base case compares two. The following:

pair_comparer(1.5, 1.5, 2, 2, 6, 6, 7)

Does not compile; the compiler complains that the base case expects 2 arguments but only 1 is provided. To fix this, we can add another variation of the function template:

template<typename T>
bool pair_comparer(T a) {
  return false;

Here, we force all odd-numbered sequences of arguments to return false, because when only a single argument is left this version is matched.

Note that pair_comparer forces both members of the compared pair to be of the exact same type. A simple variation would be to allow different types, as long as they can be compared. I'll leave this an an exercise to the interested reader.


If you're concerned with the performance of code that relies on variadic templates, worry not. As there's no actual recursion involved, all we have is a sequence of function calls pre-generated at compile-time. This sequence is, in practice, fairly short (variadic calls with more than 5-6 arguments are rare). Since modern compilers are aggressively inlining code, it's likely to end up being compiled to machine code that has absolutely no function calls. What you end up with, actually, is not unlike loop unrolling.

Compared to the C-style variadic functions, this is a marked win, because C-style variadic arguments have to be resolved at runtime. The va_ macros are literally manipulating the runtime stack. Therefore, variadic templates are often a performance optimization for variadic functions.

Type-safe variadic functions

I have mentioned printf in the beginning of the article, as an example of a variadic function that doesn't use templates. However, as we all know, printf and its kin are not type safe. If you pass a number into a %s format, bad things may happen and the compiler won't warn you about it [3].

It's pretty obvious how variadic templates enable us to write type safe functions. In the case of printf, when the implementation reaches a new formatting directive it can actually assert the type of the argument passed. This assertion won't fire at compile-time, but it will fire - and a nice error message can be generated instead of undefined behavior.

I will not discuss the implementation of a type-safe printf further - it has been rehashed many times already. For some good examples see Stroustrup's new edition of "The C++ Programming Language", or Alexandrescu's "Variadic templates are funadic" talk.

Varidic data structures

This use-case is much more interesting, IMHO, because it was something that just wasn't possible prior to introduction of C++11, at least without considerable hackery.

Custom data structures (structs since the times of C and classes in C++) have compile-time defined fields. They can represent types that grow at runtime (std::vector, for example) but if you want to add new fields, this is something the compiler has to see. Variadic templates make it possible to define data structures that could have an arbitrary number of fields, and have this number configured per use. The prime example of this is a tuple class, and here I want to show how to construct one [4].

Let's start with the type definition:

template <class... Ts> struct tuple {};

template <class T, class... Ts>
struct tuple<T, Ts...> : tuple<Ts...> {
  tuple(T t, Ts... ts) : tuple<Ts...>(ts...), tail(t) {}

  T tail;

We start with the base case - the definition of a class template named tuple, which is empty. The specialization that follows peels off the first type from the parameter pack, and defines a member of that type named tail. It also derives from the tuple instantiated with the rest of the pack. This is a recursive definition that stops when there are no more types to peel off, and the base of the hierarchy is an empty tuple. To get a better feel for the resulting data structure, let's use a concrete example:

tuple<double, uint64_t, const char*> t1(12.2, 42, "big");

Ignoring the constructor, here's a pseudo-trace of the tuple structs created:

struct tuple<double, uint64_t, const char*> : tuple<uint64_t, const char*> {
  double tail;

struct tuple<uint64_t, const char*> : tuple<const char*> {
  uint64_t tail;

struct tuple<const char*> : tuple {
  const char* tail;

struct tuple {

The layout of data members in the original 3-element tuple will be:

[const char* tail, uint64_t tail, double tail]

Note that the empty base consumes no space, due to empty base optimization. Using Clang's layout dump feature, we can verify this:

*** Dumping AST Record Layout
   0 | struct tuple<double, unsigned long, const char *>
   0 |   struct tuple<unsigned long, const char *> (base)
   0 |     struct tuple<const char *> (base)
   0 |       struct tuple<> (base) (empty)
   0 |       const char * tail
   8 |     unsigned long tail
  16 |   double tail
     | [sizeof=24, dsize=24, align=8
     |  nvsize=24, nvalign=8]

Indeed, the size of the data structure and the internal layout of members is as expected.

So, the struct definition above lets us create tuples, but there's not much else we can do with them yet. The way to access tuples is with the get function template [5], so let's see how it works. First, we'll have to define a helper type that lets us access the type of the k-th element in a tuple:

template <class T, class... Ts>
struct elem_type_holder<0, tuple<T, Ts...>> {
  typedef T type;

template <size_t k, class T, class... Ts>
struct elem_type_holder<k, tuple<T, Ts...>> {
  typedef typename elem_type_holder<k - 1, tuple<Ts...>>::type type;

elem_type_holder is yet another variadic class template. It takes a number k and the tuple type we're interested in as template parameters. Note that this is a compile-time template metaprogramming construct - it acts on constants and types, not on runtime objects. For example, given elem_type_holder<2, some_tuple_type>, we'll get the following pseudo expansion:

struct elem_type_holder<2, tuple<T, Ts...>> {
  typedef typename elem_type_holder<1, tuple<Ts...>>::type type;

struct elem_type_holder<1, tuple<T, Ts...>> {
  typedef typename elem_type_holder<0, tuple<Ts...>>::type type;

struct elem_type_holder<0, tuple<T, Ts...>> {
  typedef T type;

So the elem_type_holder<2, some_tuple_type> peels off two types from the beginning of the tuple, and sets its type to the type of the third one, which is what we need. Armed with this, we can implement get:

template <size_t k, class... Ts>
typename std::enable_if<
    k == 0, typename elem_type_holder<0, tuple<Ts...>>::type&>::type
get(tuple<Ts...>& t) {
  return t.tail;

template <size_t k, class T, class... Ts>
typename std::enable_if<
    k != 0, typename elem_type_holder<k, tuple<T, Ts...>>::type&>::type
get(tuple<T, Ts...>& t) {
  tuple<Ts...>& base = t;
  return get<k - 1>(base);

Here, enable_if is used to select between two template overloads of get - one for when k is zero, and one for the general case which peels off the first type and recurses, as usual with variadic function templates.

Since it returns a reference, we can use get to both read tuple elements and write to them:

tuple<double, uint64_t, const char*> t1(12.2, 42, "big");

std::cout << "0th elem is " << get<0>(t1) << "\n";
std::cout << "1th elem is " << get<1>(t1) << "\n";
std::cout << "2th elem is " << get<2>(t1) << "\n";

get<1>(t1) = 103;
std::cout << "1th elem is " << get<1>(t1) << "\n";

Variadic templates for catch-all functions

Here is another example I find interesting. It's different from the ones already shown in the article, because it doesn't really use the traditional recursive approach of implementing variadic templates. Rather, it uses them to express the "any template parameters can go here" concept.

Say we want to write a function that can print out standard library containers. We want it to work for any container, and we also want the user to type as little as possible, so we don't want to act on iterators. We just want print_container(c) to work for any container c. Here's a first approach:

template <template <typename, typename> class ContainerType,
          typename ValueType,
          typename AllocType>
void print_container(const ContainerType<ValueType, AllocType>& c) {
  for (const auto& v : c) {
    std::cout << v << ' ';
  std::cout << '\n';

Many of the STL containers are templates that can be parameterized by the value type and an allocator type; for example vector, list, deque, and so on. So we can write:

std::vector<double> vd{3.14, 8.1, 3.2, 1.0};

std::list<int> li{1, 2, 3, 5};

And this works as expected. However, if we try to use it for map, we get a compile error:

std::map<std::string, int> msi{{"foo", 42}, {"bar", 81}, {"bazzo", 4}};
error: no matching function for call to 'print_container'
note: candidate template ignored: substitution failure :
      template template argument has different template
      parameters than its corresponding template template parameter

This is because map is a template parameterized by 4 template arguments, not 2. The same problem would occur for a set, which has 3 template arguments. This is annoying - while the contents of the print_container function would be the same for all these containers, the signature has to be different. What can we do without duplicating code? Variadic templates for the rescue:

template <template <typename, typename...> class ContainerType,
          typename ValueType, typename... Args>
void print_container(const ContainerType<ValueType, Args...>& c) {
  for (const auto& v : c) {
    std::cout << v << ' ';
  std::cout << '\n';

What this says is - ContainerType is a template template parameter with any amount of template parameters itself. We don't care really, as long as the compiler can type-deduce them at the call. This version of the function will work for map, set, unordered_map and other containers [6]. One small addition we have to make to support mappings is:

// Implement << for pairs: this is needed to print out mappings where range
// iteration goes over (key, value) pairs.
template <typename T, typename U>
std::ostream& operator<<(std::ostream& out, const std::pair<T, U>& p) {
  out << "[" << p.first << ", " << p.second << "]";
  return out;

Variadic templates for forwarding

A somewhat related example is templates that don't do much on their own, but have to forward all their arguments to some other template or function. This turns out to be very useful because C++ has a commonly used construct that is inherently "variadic" when viewed from a template parameter point of view - the constructor. Given a generic type T, to invoke the constructor of T, we may need to pass in an arbitrary number of arguments. Unlike function types that specify their arguments at compile time, given just a generic type T we don't know which constructor(s) it has and how many arguments the constructor accepts.

A very important example of this is the std::make_unique function, available in the standard library since C++14. We want to be able to use it as follows:

std::unique_ptr<FooType> f = std::make_unique<FooType>(1, "str", 2.13);

FooType is an arbitrary type and can be constructed in arbitrary ways. How does make_unique know the signature of its constructor? With variadic templates, it doesn't have to know! Here's how make_unique is typically implemented:

template<typename T, typename... Args>
unique_ptr<T> make_unique(Args&&... args)
    return unique_ptr<T>(new T(std::forward<Args>(args)...));

Ignore the && syntax and std::forward for now; I will cover them in a future article. What's important for the sake of our current discussion is the use of a variadic template to convey "any amount of arguments can go here" and passing them through to the constructor of c in the new expression.