Type Mapping
Your TypeScript types become C++ types. Here’s how the common ones line up. You rarely need to think about this — it just works — but the table below is handy when you want to know exactly what’s happening.
Primitive Types
| TypeScript | C++ | Notes |
|---|---|---|
number | int | Default; widened to long if value exceeds int range |
boolean | bool | |
string | const char* or a small fixed-size text buffer | On Arduino, text is stored in a fixed-size buffer rather than on the heap |
void | void | |
double | double | |
long | long | |
uint8_t | uint8_t | Direct pass-through |
int16_t | int16_t | Direct pass-through |
size_t | size_t | Direct pass-through |
Type Aliases from cuttlefish-env.d.ts
Scaffolded projects include C-style numeric type aliases available in TypeScript:
const count: uint8_t = 42;
const bigValue: int32_t = 100000;These map directly to their C++ equivalents — no conversion needed.
Composite Types
Arrays
Array<T>, ReadonlyArray<T>, and T[] become plain C-style arrays on AVR (small and predictable, the way embedded code expects):
// TypeScript
const values: number[] = [1, 2, 3];// C++ (AVR)
int values[] = {1, 2, 3};On more capable boards, they become std::vector<T>.
Negative numeric literals inside typed arrays are compile-time-safe and stay as initializers — they are not migrated into setup().
Objects
Object literals become anonymous C structs with generated type names:
// TypeScript
const config = { baud: 9600, parity: 'none' };// C++
struct _config_t { int baud; const char* parity; };
_config_t config = {9600, "none"};Nested objects generate recursively defined structs (deepest first). Top-level object literals used as destructuring sources are emitted as globals.
Enums
// TypeScript
enum PinMode { Input, Output, InputPullup }// C++
enum class PinMode { Input, Output, InputPullup };Collections
| TypeScript | C++ |
|---|---|
Set<T> / ReadonlySet<T> | std::set<T> |
Map<K,V> / ReadonlyMap<K,V> | std::map<K,V> |
Record<K,V> | std::map<K,V> |
Functions
const handler: (pin: number) => void = (pin) => { /* ... */ };// C++
std::function<void(int)> handler = [](int pin) { /* ... */ };On Arduino, std::function is normalized to C function pointer syntax.
Unions
Union types map to std::variant:
type Result = string | number;// C++
std::variant<std::string, int> result;Union with null or undefined strips the null side:
type MaybeNumber = number | null;
// C++: int (null handled via cuttlefish_nullish)Template Literals
A string with ${...} placeholders becomes a small text buffer filled with the formatted result:
// TypeScript
const msg = `Temperature: ${temp}C`;// C++
char __tc_str_1[32];
snprintf(__tc_str_1, sizeof(__tc_str_1), "Temperature: %dC", temp);The + operator on strings works the same way:
const msg = "Value: " + String(value) + " units";Nullish Coalescing (??)
The ?? operator becomes a safe check that treats 0 and false as real values (so 0 ?? 42 is 0, not 42):
// TypeScript
const value = input ?? 42;// C++
int value = cuttlefish_nullish<int, int>(input, 42);Optional Chaining (?.)
The ?. operator becomes a safe guard:
// TypeScript
const len = obj?.length;// C++
auto len = cuttlefish_exists(obj) ? obj.length : /* default */;Equality Operators
TypeScript === and !== become C++ == and !=:
// TypeScript
if (value === 42) { /* ... */ }// C++
if (value == 42) { /* ... */ }Ownership Type Erasure
Owned<T>, Shared<T>, and Mutable<T> are unwrapped during type resolution — the inner T is used as the C++ type. Shared<T> adds const in C++ for compiler-enforced read-only access. No wrappers are generated. (See the Ownership docs.)
Classes
new ClassName() instances become value types or pointers depending on context. Cuttlefish generates proper C++ class definitions with constructors matching the TypeScript source.
Forward Declarations
When top-level code calls helper functions defined later in the file, Cuttlefish automatically inserts forward declarations before setup(). Default arguments appear on the declaration, not the definition:
// Forward declaration (before setup)
int calculateAverage(int a, int b = 0);
void setup() { /* ... */ }
void loop() { /* ... */ }
// Definition (after loop)
int calculateAverage(int a, int b) {
return (a + b) / 2;
}Control Flow
for-of
// TypeScript
for (const item of items) { /* ... */ }// C++
for (const auto item : items) { /* ... */ }for-in
When object keys are known at compile time:
const char* _ki_obj_keys[] = { "name", "value" };
for (int _ki_obj = 0; _ki_obj < 2; _ki_obj++) { /* ... */ }try/throw
On Arduino, throw maps to a halt macro since exceptions are disabled:
cuttlefish_halt("PANIC");A diagnostic is emitted if try-catch is used on a platform with -fno-exceptions.
Modulo on Float
The % operator on floating-point values is rewritten to fmod():
const remainder = value % 3.14;double remainder = fmod(value, 3.14);⚙️ Advanced details — framework overrides and formatting internals
How std::string is rewritten on Arduino
The framework strategy may override the default type mapping. @typecad/framework-arduino rewrites std::string to __tc_str_ptr, a custom fixed-size stack-allocated string struct that avoids heap allocation.
Format specifier inference
The transpiler automatically picks the right printf format based on the expression type:
| Expression type | Format specifier |
|---|---|
Integer literal / int | %d |
Float literal / float | %g or %.Nf |
| Boolean | %s with "true" / "false" ternary |
| String | %s |
long / int32_t | %ld |
AVR float handling
On AVR, %f in snprintf is disabled to save flash. Instead, dtostrf() is used:
// AVR float in template literal
char __tc_float_1[16];
dtostrf(temperature, 0, 2, __tc_float_1);
snprintf(buf, sizeof(buf), "Temp: %sC", __tc_float_1);The cuttlefish_nullish helper
The helper template behind ??:
template<typename T, typename U>
inline T cuttlefish_nullish(T a, U b) {
return (a != (T)CUTTLEFISH_UNDEFINED) ? a : (T)b;
}This preserves 0 as a real value (only CUTTLEFISH_UNDEFINED triggers the fallback).
On This Page