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
- 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.
-
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:
- Object-like macros, for example whose body names a variable or type
- Function-like macros that have parameters
- Predefined macros like
__FILE__or__LINE__
Alternatives considered
- Reusing Swift/C implementation
- Manually scanning tokens
- Importing macros that refer to enum constants and
constexprvariables as constant values, instead of aliases in Carbon