Importing C/C++ macros

Table of contents

Overview

C/C++ object-like macros are frequently used in APIs of standard and low-level C++ libraries to define constants (such as error codes in <errno.h>). To support seamless interoperability, an object-like C/C++ macro that evaluates to a constant expression is imported into Carbon as a constant.

For example:

C++:

#define BUFFER_SIZE 4096

Carbon:

// Cpp.BUFFER_SIZE is imported as a value of type i32 with a value of 4096.
let a: i32 = Cpp.BUFFER_SIZE;

Details

When importing an object-like macro, the tokens of the macro’s replacement list are evaluated as a C++ constant expression in the global C++ namespace, and the resulting constant value is imported into the Cpp Carbon namespace. Its type is mapped to a Carbon type following the Carbon <-> C++ type mapping rules, and its expression category is determined by the C++ value category: lvalues are imported as references, and rvalues are imported as values.

For example:

C++:

enum class Color { Red = 1, Green = 2 };
#define GREEN_COLOR Color::Green

constexpr int kValue = 123;
#define VALUE kValue

Carbon:

// Cpp.GREEN_COLOR is equal to Cpp.Color.Green, and has type Cpp.Color.
let b: Cpp.Color = Cpp.GREEN_COLOR;

// Cpp.VALUE is an alias to kValue.
let a: i32 = Cpp.VALUE;

Note that this means that an imported macro can behave differently in Carbon when used inside an expression. The following C++ program prints 7, since the macro is expanded before the multiplication operation; 2 * 1 + 2 + 3 is evaluated as (2 * 1) + 2 + 3:

#include <iostream>

#define ADDITION 1+2+3

int main() {
std::cout << (2 * ADDITION) << '\n';
}

While the following Carbon program prints 12, since Cpp.ADDITION is treated as a constant with value 6:

import Core library "io";

import Cpp inline "#define ADDITION 1+2+3";

fn Run() {
Core.Print(2 * Cpp.ADDITION);
}

Future work: It may be possible to evaluate the macro definition in a child namespace, rather than the global C++ namespace.

Implementation

  1. Name lookup: When a C++ macro name is encountered in Carbon, it is looked up first, before other names. Following C++ rules, this ensures that the macro is found even if there is a non-macro entity (such as a variable) with the same name.
  2. Macro import: If an eligible macro is found, the compiler (effectively) attempts to generate a C++ helper declaration and imports it if that succeeds:

    constexpr inline decltype(auto) __carbon_import_MY_MACRO = (MY_MACRO);
    

    This delegates parsing and type/value evaluation to Clang.

Future work

Whether Carbon will support other macro forms is still to be determined:

Alternatives considered

References