SHRT

syntax sugar for c++
Anton Veselskyi
Try it live →

What it is

shrt is a transpiler — a thin layer of syntax sugar that compiles down to plain C++. Any valid C++ is valid shrt. You can mix freely.

The premise is simple:

The language reserves single letters for itself (i, c, s, e, r, f, w, …) so the keywords become mundane and clear, while your variables and functions must be two characters or longer — forcing you to write what you actually mean.

No more int n or auto x hiding in a function.

Be expressive. Be verbose. Be readable.

Anything you don't want shortened, just write the original C++ keyword — e means enum class by default, but if you want a plain C-style enum, type it out. shrt never gets in your way; it only ever adds.

The Rules

Rule #1 — One letter belongs to the language

Variable, function, and type names must be at least two characters. Single letters are reserved keywords. Template type parameters (T, U) are exempt.

File extensions

Source files use .shrt. The transpiler emits .shrt.cpp as an intermediate before the C++ compiler picks it up.

Includes

i vector#include <vector>  ·  li mylib.h#include "mylib.h"

The $ prefix — implicit std::

Prefix any identifier with $ and shrt prepends std:: for you. $vector<int>, $cout, $string.

Keyword map

i#include <...>
li#include "..."
cclass
sstruct
eenum class
aauto
ffor
wwhile
elelse
vvirtual
nnamespace
uusing
kconst
ddelete
ooverride
rreturn
bbreak
contcontinue
keconstexpr
opoperator
pubpublic
priprivate
proprotected
whichswitch
pstd::cout << … << endl
deferscope-exit cleanup
nilnullptr

Optional parens on conditions

if and w conditions don't need parentheses. The condition is everything between the keyword and the opening {. Parens still work if you want them.

if health <= 0
{
    die();
}

w running
{
    tick();
}

switch → which, case is implicit

which replaces switch, the case keyword disappears, just write the label value followed by a colon.

which signal
{
    1: start(); b;
    2: stop();  b;
    default: r;
}

The Elvis operator — ?? & ??=

Truthiness-checked fallback. If the left side is falsy, the right side fires. The right side can be an expression (yields a value) or a statement (returns, calls, breaks — anything). Inspired by GCC's ?:, Kotlin's guards, and bash's ${var:-default}.

Player* player = find_player(idx);
player ?? r nullptr;

player->name ??= "Anonymous";

a health = player->hp ?? 100;

The => arrow — two meanings, one mental model

=> always means "what follows is the result."

// expression body
int square(int num) => num * num;

// standalone lambda
a bump = => counter + 1;
a snapshot = [=] => counter;

Null-safe access — ?.

The natural sibling of ??. Chains member access without crashing on null pointers along the way. If any link in the chain is null, the whole expression evaluates to null. Borrowed from Kotlin, Swift, C#.

Pairs gorgeously with the Elvis: player?.name ?? "Anonymous" reads almost as English.

a weapon_name = player?.weapon?.name ?? "fists";

shop?.buy("potion");    // silently skipped if shop is null

Player* hero = roster?.find(idx);

String interpolation — $"…"

Reuses the $ sigil. Anything inside {…} is an expression — interpolated into the string. Transpiles to std::format, so it's type-safe and as fast as printf.

a greeting = $"Hello {name}, you have {coins} gold!";

a report   = $"HP {player->hp} / {player->max_hp}";

a math     = $"sum = {x + y}";

The p print — a cout wrapper

Thin wrapper around std::cout << … << std::endl. Anything streamable into cout can be piped into p. Composes naturally with $"…" interpolation.

p "starting up";

p $"Hello {name}, gold {coins}";

p player?.name ?? "Anonymous";

defer — inline RAII cleanup

Stolen from Go, Zig, and Swift. The statement (or block) following defer is executed at scope exit — no matter how the scope exits (normal return, early return, exception). Fits C++ RAII philosophy without writing a wrapper class for one-off resources.

Transpiles into an anonymous scope-guard whose destructor fires the deferred work — same trick libraries like gsl::finally and scope_guard use, just baked into the language.

void process(k char* path)
{
    a* file = $fopen(path, "r");
    file ?? r;
    defer $fclose(file);

    // ...work with file...
    // fclose fires automatically on any exit
}

Side by side

Ten snippets — C++ on the left, shrt on the right. The scroll on the right is always shorter — that's the point.

Hello, World
Kernighan & Ritchie, 1972
C++
#include <iostream>

int main()
{
    std::cout << "Hello, World!" << std::endl;
    return 0;
}
SHRT
i iostream

int main()
{
    p "Hello, World!";
    r 0;
}
Fibonacci
Leonardo of Pisa, 1202 · => expression body
C++
int fibonacci(int num)
{
    if (num <= 1) return num;
    return fibonacci(num - 1)
         + fibonacci(num - 2);
}
SHRT
int fibonacci(int num) =>
    num <= 1
        ? num
        : fibonacci(num - 1) + fibonacci(num - 2);
Euclidean GCD
Euclid, ~300 BC · optional parens, w loop
C++
int gcd(int alpha, int beta)
{
    while (beta != 0)
    {
        int temp = beta;
        beta = alpha % beta;
        alpha = temp;
    }
    return alpha;
}
SHRT
int gcd(int alpha, int beta)
{
    w beta != 0
    {
        int temp = beta;
        beta = alpha % beta;
        alpha = temp;
    }
    r alpha;
}
Linked List Traversal
Newell & Shaw, 1955 · ?? as a guard
C++
struct Node
{
    int value;
    Node* next;
};

void print_list(Node* head)
{
    if (!head) return;
    while (head != nullptr)
    {
        std::cout << head->value << " ";
        head = head->next;
    }
}
SHRT
s Node
{
    int value;
    Node* next;
};

void print_list(Node* head)
{
    head ?? r;
    w head != nil
    {
        p head->value;
        head = head->next;
    }
}
Singleton Pattern
Gang of Four, 1994 · ??= lazy assignment
C++
class Logger
{
private:
    static Logger* instance;
    Logger()
    {
    }
public:
    static Logger* get_instance()
    {
        if (instance == nullptr)
        {
            instance = new Logger();
        }
        return instance;
    }
};
SHRT
c Logger
{
pri:
    static Logger* instance;
    Logger()
    {
    }
pub:
    static Logger* get_instance()
    {
        instance ??= new Logger();
        r instance;
    }
};
2D Point
classic value type · => expression bodies
C++
class Point
{
    int px_, py_;
public:
    Point(int px, int py) : px_(px), py_(py)
    {
    }

    int get_x() const
    {
        return px_;
    }
    int get_y() const
    {
        return py_;
    }
    int sum()   const
    {
        return px_ + py_;
    }
};
SHRT
c Point
{
    int px_, py_;
pub:
    Point(int px, int py) : px_(px), py_(py)
    {
    }

    int get_x() k => px_;
    int get_y() k => py_;
    int sum()   k => px_ + py_;
};
Direction Enum
Tony Hoare's case dispatch, 1965 · e + which + implicit case
C++
enum class Direction
{
    North,
    South,
    East,
    West
};

int delta_x(Direction dir)
{
    switch (dir)
    {
        case Direction::East:  return  1;
        case Direction::West:  return -1;
        default:                  return  0;
    }
}
SHRT
e Direction
{
    North,
    South,
    East,
    West
};

int delta_x(Direction dir)
{
    which dir
    {
        Direction::East:  r  1;
        Direction::West:  r -1;
        default:           r  0;
    }
}
Group Anagrams
LeetCode #49, the classic interview round · $ everywhere
C++
#include <vector>
#include <unordered_map>
#include <string>
#include <algorithm>

class Solution
{
public:
    std::vector<std::vector<std::string>>
    groupAnagrams(std::vector<std::string>& strs)
    {
        std::unordered_map<
            std::string,
            std::vector<std::string>
        > groups;

        for (const std::string& word : strs)
        {
            std::string key = word;
            std::sort(key.begin(), key.end());
            groups[key].push_back(word);
        }

        std::vector<std::vector<std::string>> result;
        for (auto& entry : groups)
        {
            result.push_back(std::move(entry.second));
        }
        return result;
    }
};
SHRT
i vector
i unordered_map
i string
i algorithm

c Solution
{
pub:
    $vector<$vector<$string>>
    groupAnagrams($vector<$string>& strs)
    {
        $unordered_map<
            $string,
            $vector<$string>
        > groups;

        f (k $string& word : strs)
        {
            $string key = word;
            $sort(key.begin(), key.end());
            groups[key].push_back(word);
        }

        $vector<$vector<$string>> result;
        f (a& entry : groups)
        {
            result.push_back($move(entry.second));
        }
        r result;
    }
};
Player Inspector
gamedev sketch · ?. + $"…" + p, all at once
C++
#include <iostream>
#include <format>
#include <string>

struct Weapon
{
    std::string name;
    int damage;
};

struct Player
{
    std::string name;
    Weapon* weapon;
    int hp;
};

void inspect(Player* player)
{
    if (!player) return;

    std::string weapon_name = player->weapon
        ? player->weapon->name
        : "fists";
    int weapon_dmg = player->weapon
        ? player->weapon->damage
        : 1;

    std::cout << std::format(
        "{} wields {} ({}dmg) — {}hp",
        player->name, weapon_name, weapon_dmg, player->hp
    ) << std::endl;
}
SHRT
i iostream
i format
i string

s Weapon
{
    $string name;
    int damage;
};

s Player
{
    $string name;
    Weapon* weapon;
    int hp;
};

void inspect(Player* player)
{
    player ?? r;

    a weapon_name = player->weapon?.name ?? "fists";
    a weapon_dmg  = player->weapon?.damage ?? 1;

    p $"{player->name} wields {weapon_name} ({weapon_dmg}dmg) — {player->hp}hp";
}
Grid Pathfinding (BFS)
gamedev classic · e + s + ?? guard + standalone => lambda + expression body
C++
#include <queue>
#include <vector>

enum class Tile
{
    Floor,
    Wall,
    Goal
};

struct Cell
{
    int px, py;

    bool operator==(const Cell& other) const
    {
        return px == other.px && py == other.py;
    }
};

std::vector<Cell> bfs(
    const std::vector<std::vector<Tile>>& grid,
    Cell start)
{
    std::queue<Cell> frontier;
    frontier.push(start);

    auto in_bounds = [&]()
    {
        return !grid.empty();
    };
    if (!in_bounds()) return {};

    while (!frontier.empty())
    {
        Cell cur = frontier.front();
        frontier.pop();
        if (grid[cur.py][cur.px] == Tile::Goal)
        {
            return {cur};
        }
        // expand neighbors...
    }
    return {};
}
SHRT
i queue
i vector

e Tile
{
    Floor,
    Wall,
    Goal
};

s Cell
{
    int px, py;

    bool op==(k Cell& other) k =>
        px == other.px && py == other.py;
};

$vector<Cell> bfs(
    k $vector<$vector<Tile>>& grid,
    Cell start)
{
    $queue<Cell> frontier;
    frontier.push(start);

    a in_bounds = => !grid.empty();
    in_bounds() ?? r {};

    w !frontier.empty()
    {
        Cell cur = frontier.front();
        frontier.pop();
        if grid[cur.py][cur.px] == Tile::Goal
        {
            r {cur};
        }
        // expand neighbors...
    }
    r {};
}