@def directive

Simple macros

A macro can be defined and then referenced any number of times further down in the translation unit:

Before translationAfter translation
@def m = 100

// This is a comment
int f() { return m+m/2; }



// This is a comment
int f() { return 100+100/2; }

We say that macro m has been invoked twice in the above example. Often a macro is defined in a header file so it can be invoked from multiple source files. We refer to the substitution string 100 as the body of m.

The translation maintains existing indentation and comments. Macro definitions are stripped away, and all invocations of a macro after the point of definition result in substitution by the body.

Macro arguments

In similar fashion to C/C++ #define macro definitions, xcpp macros can take any number of formal arguments. By default the macro expander performs substitution without regard for the semantics of C/C++ programs. The following example shows how inappropriate usage can lead to surprises because of a lack of bracketing of expressions:

Before translationAfter translation
@def add(x,y) = x+y
int f() { return 3*add(5,2); }


int f() { return 3*5+2; }

Macro return types

The return type of a macro can be specified. In the following example it is stipulated that add(x,y) returns an int. This causes xcpp to perform calculations at compile time in order to coerce the return value.

Before translationAfter translation
@def int add(x,y) = x+y
int f() { return 3*add(5,2); }


int f() { return 3*7; }

This can have a number of advantages:

Macro argument types

The types of the formal arguments of a macro can optionally be specified. This leads to eager evaluation of the arguments by the xcpp preprocessor at the point of invocation. The advantages described above for strong typed return values also apply to strongly typed formal arguments. Here is an example:

Before translationAfter translation
@def multiply(int x,int y) = x*y
int f() { return multiply(1+2,4); }


int f() { return 3*4; }

The following example illustrates strong typing of both the return value and arguments of a macro:

Before translationAfter translation
@def int multiply(int x,int y) = x*y
int f() { return multiply(1+2,4); }


int f() { return 12; }

Rules for delimiting the body

In the directive @def x = y, we call y the body. The body is delimited in a number of different ways. In the following example, macros named w1,w2 and w3 are all equivalent:

Before translationAfter translation
@def w1 = 5
@def w2 = {5}
@def w3 =
{
    5
}
int f() { return w1+w2+w3; }







int f() { return 5+5+5; }

The extent of the body is determined before considering its macro expansion. The xcpp preprocessor uses the lexical scanner to help determine the extent of the body. This allows it to ignore braces inside // or /*...*/ comments or in single or double quoted strings. From a position just after the ’=’, it scans past space and tab characters on that line. If the next character is not a linefeed or left brace then it sets the body to be all the remaining characters on that line (not including the linefeed). Otherwise the body is assumed to be an indented block of text delimited (non-inclusively) by braces. Nested braces are allowed within the block, as long as braces pair up correctly (xcpp counts braces to determine the end of the block). The body doesn’t include the final linefeed - i.e. on the last line immediately before the final right brace.

In rare circumstances when there’s a need to workaround the counting of braces, @unstr can be used (this directive is described in section 6.3). For example:

Before translationAfter translation
@def leftBrace = @unstr('{')
@def rightBrace = @unstr('}')
@def m = rightBrace@@leftBrace@@leftBrace
const char* f()
{
    {m}}
    return @str(m);
}




const char* f()
{
    {}{{}}
    return "}{{";
}

Indenting of replacement text

The xcpp preprocessor applies an indentation to an entire block of text under macro substitution. The effect is that the output of the preprocessor is often conveniently formatted. For example:

Before translationAfter translation
@def m(x,y,b) =
{
    if (x < y)
    {
        return b;
    }
}
@def loop(i,n,body) =
{
    for (int i=0 ; i < n ; ++i)
    {
        body
    }
}
bool f(int* x, int nx, int* y, int ny)
{
    loop(i,nx,loop(j,ny,m(x[i],y[j],true)))
    return false;
}
bool f(int* x, int nx, int* y, int ny)
{
    for (int i=0 ; i < nx ; ++i)
    {
        for (int j=0 ; j < ny ; ++j)
        {
            if (x[i] < y[j])
            {
                return true;
            }
        }
    }
    return false;
}

Nested macro definitions

Macro definitions can be nested. The following example shows how macro m defines a local macro called y. Local macros are not accessible outside the scope of the containing macro.

Before translationAfter translation
@def m(x) =
{
    @def y = 2
    x+y
}
int f()
{
    int y = 3;
    return m(1)+y;
}






int f()
{
    int y = 3;
    return 1+2+y;
}

Back-quoted arguments to macros

An argument to a macro can be back-quoted in order to ensure it is parsed as a single indivisible argument to bind to the formal argument of the macro. For example, it is necessary to back-quote the argument map<K,V> because it contains commas, and the macro expander doesn’t count angled brackets to delimit terms when a macro is invoked.

Before translationAfter translation
@def m(type) = void f(const type&);
template <class K,class V>
m(`map<K,V>`)


template <class K,class V>
void f(const map<K,V>&);

Exported macros

Macros have a scope in which they are available to be invoked. Macros at file scope are only local to the file in which they appear. For example if a header file defines a macro w like this


@def w = 1

then w is only visible within that header file, and not to other files than import that header

To allow a macro to be visible in other files it needs to be exported using the '+' qualifier. A macro can be conditionally exported from a nested scope. For example the following code exports macros named x and y (but not z).


@def+ x = 2

@if (true)
{
    @def+ y = 3
}

@if (false)
{
    @def+ z = 4
}