Viper is a source-to-source transpiler that converts typed Python into C++17.
Write Python with full type annotations, get clean, idiomatic C++ output — no runtime, no GC, no interpreter.
python viper.py source.py -o output.cpp
g++ -std=c++17 -O2 output.cpp -o output
Multiple source files are supported (merged into a single translation unit):
python viper.py main.py utils.py -o output.cpp
Python 3.10+ (to run the transpiler)
A C++17-capable compiler (g++, clang++, MSVC)
Legend: ✅ supported · ⚠️ partial / caveat · ❌ not supported
Python
C++
Status
int
int
✅
float
double
✅
bool
bool
✅
str
std::string
✅
bytes / bytearray
std::vector<uint8_t>
✅
None (return)
void
✅
Python
C++
Status
list[T]
std::vector<T>
✅
dict[K, V]
std::unordered_map<K, V>
✅
set[T]
std::unordered_set<T>
✅
frozenset[T]
std::unordered_set<T> (read-only)
✅
tuple[A, B, ...] (heterogeneous)
std::tuple<A, B, ...>
✅
tuple[T, ...] (homogeneous)
std::vector<T>
✅
deque[T]
std::deque<T>
✅
defaultdict[K, V]
std::unordered_map<K, V> (C++ operator[] provides default)
✅
OrderedDict[K, V]
std::map<K, V> (ordered by key)
✅
Counter[T]
std::unordered_map<T, int>
✅
Feature
C++
Status
lst[i] (positive index)
operator[]
✅
lst[-1] / lst[-N]
size() - N
✅
lst[a:b] / lst[a:] / lst[:b]
subrange copy
✅
lst[::-1] / lst[a:b:step]
step-slice lambda
✅
lst[a:b] = new_list (slice assignment)
erase + insert
✅
s[i] (string char)
std::string(1, s[i])
✅
s[-1] / s[-N]
negative char index
✅
s[a:b] / s[a:] / s[:b]
.substr()
✅
s[::-1] (reverse string)
std::string(rbegin, rend)
✅
dq[i] (deque index)
operator[]
✅
obj[k] on user class
calls __getitem__
✅
obj[k] = v on user class
calls set_item() (__setitem__)
✅
Feature
C++
Status
Optional[T] / T | None
std::optional<T>
✅
Optional[T] self-referential (e.g. nxt: Optional["Node"] inside Node)
❌ std::optional requires complete type; use pointer / shared_ptr instead
Union[X, Y] / X | Y
std::variant<X, Y>
✅
Callable[[A, B], R]
std::function<R(A, B)>
✅
TypeVar + generic functions
template<typename T> function
✅
Generic[T] classes
template<typename T> class
✅
ClassVar[T]
static T + out-of-class definition
✅
Type alias (MyList = list[int])
resolved at transpile-time
✅
String forward references ("ClassName")
resolved if class is known
✅
isinstance(x, T) on Union/variant
std::holds_alternative<T>
✅
isinstance(x, T) on ordinary classes
compile-time type walk
✅
isinstance(x, T) runtime polymorphism
❌ needs virtual dispatch / RTTI
Any
std::any + std::any_cast via cast(T, val)
⚠️ basic
Feature
C++
Status
Typed functions
direct translation
✅
Default parameters
direct translation
✅
*args: T (variadic)
std::vector<T> param
✅
f(*lst) call unpacking
expands to lst[0], lst[1], ...
✅
**kwargs
std::unordered_map<string,string> with empty default {}
⚠️ basic (string values only)
f(**dict) call unpacking
Passes dict as kwargs argument to functions with **kwargs param
✅
Keyword-only params (def f(*, x: int = 0))
regular C++ param with default
✅
Keyword args in calls (f(y=3, x=4))
reordered to positional at compile-time
✅
Nested functions (def inside def)
auto f = [=](...) -> T { } (captures by value for safe return)
✅
Functions as values (Callable)
std::function<...>
✅
Generator / yield → Iterator[T]
collects into std::vector<T>
✅
@staticmethod
static
✅
@classmethod
static (drops cls)
✅
@property getter
method name()
✅
@x.setter
method set_name(val)
✅
@abstractmethod
virtual ... = 0
✅
@dataclass
auto __init__, operator==, to_string(); default values, frozen=True, __post_init__ supported
✅
@overload stubs
skipped; actual implementation transpiled
✅
Lambda with annotated args
[&](T x) -> R { return ...; }
✅
Lambda unannotated (lambda x: ...)
Infers types from context (Callable annotation, function param)
⚠️ context-dependent
Lambda capture mode
[&] for inline; [=] when stored as std::function (avoids dangle)
✅
Lambda in map() / filter()
Infers arg type from list element type
✅
C++ keyword collision
double → double_, auto → auto_, etc. (appends _)
✅
Feature
C++
Status
if / elif / else
if / else if / else
✅
while
while
✅
for x in range(...)
for (int x = ...)
✅
for x in list / set / deque / frozenset
range-for
✅
for x in dict
for (auto& [x, _] : d)
✅
for c in str
char-by-char with std::string wrapper
✅
for x in bytes
for (uint8_t x : b)
✅
for k, v in dict.items()
structured binding
✅
for i, v in enumerate(lst, start=N)
index + element
✅
for a, b in zip(l1, l2)
index-based min-size
✅
for a, b, c in zip(l1, l2, l3, ...)
3+ iterables
✅
for x in reversed(lst)
reverse iterators
✅
for x in user_obj (with __iter__/__next__)
while-loop with _StopIteration_
✅
for x in heterogeneous tuple
❌ std::tuple is not iterable at runtime
for / while with else
block after loop
✅
break / continue
direct
✅
match / case (Python 3.10+)
if / else if chain
✅
Ternary x if c else y
c ? x : y
✅
Walrus :=
side-effect assignment + return
✅
Chained comparisons 1 < x < 10
(1 < x && x < 10)
✅
with (RAII / context manager)
scoped block
✅
with on user context manager
__enter__/__exit__ + try/catch
✅
with A() as a, B() as b:
nested single-item with
✅
List / dict / set comprehension
lambda with push_back / insert
✅
Multi-for comprehension
nested loops
✅
Feature
C++
Status
Class with typed fields and methods
class with public members
✅
Single inheritance
: public Parent
✅
Multiple inheritance
: public A, public B; MRO-style method lookup
✅
super().__init__(args)
Parent::Parent(args)
✅
Parent.__init__(self, args) (Python 2-style super call)
Parent::Parent(args)
✅
super().method(args)
Parent::method(args)
✅
Virtual / override methods
virtual / override
✅
__init__
constructor
✅
__del__
destructor ~ClassName()
✅
__str__ / __repr__
to_string()
✅
__eq__, __lt__, __le__, __gt__, __ge__, __ne__
operator==, <, etc.
✅
__add__, __sub__, __mul__, __truediv__, __mod__
operator+, -, *, /, %
✅
__iadd__, __isub__, __imul__, __itruediv__
operator+=, -=, *=, /=
✅
__neg__, __pos__
unary operator-, +
✅
__len__
size()
✅
__bool__
explicit operator bool() const
✅
__int__ / __float__
explicit operator int/double() const
✅
__getitem__
T operator[](K key) const
✅
__setitem__
void set_item(K key, V val)
✅
__contains__ (x in obj)
bool contains(T val) const
✅
__hash__
_hash_() + std::hash<T> specialization
✅
__iter__ / __next__
_iter_() / _next_() + sentinel loop
✅
Enum / IntEnum
enum class
✅
StrEnum / string-valued Enum
struct with static constexpr const char* members
✅
IntEnum → int compatibility
(int)Enum::MEMBER cast; IntFlag also supported
✅
NamedTuple
struct with positional constructor
✅
Inherited constructors
using Parent::Parent; when subclass has no __init__
✅
Polymorphic assignment (a: Base = Derived(...))
auto a = Derived(...) to avoid slicing
✅
Polymorphic parameters (def f(a: Base))
const Base& reference; virtual methods auto-marked const if they don't mutate self
✅
Protocol
transparent base — structural typing NOT enforced
⚠️
__slots__
❌ not needed (all fields declared explicitly)
@staticmethod / @classmethod
static methods; callable as ClassName::method(...)
✅
Feature
C++
Status
try / except / else / finally
try / catch
✅
raise ExcType(msg)
throw cpp_exc(msg)
✅
raise (re-raise)
throw
✅
raise StopIteration()
throw _StopIteration_{}
✅
except (A, B)
multiple catch blocks (body duplicated)
✅
assert cond / assert cond, msg
if (!cond) throw std::runtime_error(...)
✅
Custom exception classes
inherits std::exception subclass
✅
Exception mapping
ValueError → std::invalid_argument, IndexError → std::out_of_range, etc.
✅
Feature
C++
Status
+, -, *, /, //, %, **
direct / std::pow
✅
&, |, ^, ~, <<, >> (bitwise)
direct
✅
not, and, or
!, &&, ||
✅
is None / is not None
!has_value() / .has_value()
✅
in / not in list / deque
std::find
✅
in / not in dict
.count()
✅
in / not in set / frozenset
.count()
✅
in / not in str
.find() != npos
✅
in / not in user class
calls __contains__
✅
str * int / int * str
repeat string
✅
list * int / int * list
repeat list
✅
list + list
concatenation
✅
Augmented assign (+=, -=, …) on numerics / str
direct
✅
Augmented assign on subscript (d[k] += v)
direct
✅
Augmented assign via user dunder (__iadd__ etc.)
operator+= etc.
✅
Set operators (|, &, -, ^, |=, etc.)
lambda inline
✅
User-class binary operators (__add__, __eq__, etc.)
via operator overload
✅
Python
C++
Status
print(...)
std::cout << ...
✅
print(..., sep=s, end=e)
sep/end keyword args
✅
len(x)
(int)x.size()
✅
range(n) / range(a,b) / range(a,b,s)
for loop or std::vector<int>
✅
abs(x)
std::abs(x)
✅
min(a, b) / max(a, b)
std::min / std::max
✅
min(lst) / max(lst)
*std::min_element / *std::max_element
✅
min(lst, key=f) / max(lst, key=f)
custom comparator lambda
✅
sum(lst) / sum(lst, start)
accumulate lambda
✅
any(lst) / all(lst)
short-circuit lambda
✅
sorted(lst)
copy + std::sort
✅
sorted(lst, key=f, reverse=True)
key / reverse
✅
reversed(lst)
reverse iterators
✅
enumerate(lst) / enumerate(lst, start=N)
index + element
✅
zip(l1, l2) / zip(l1, l2, l3, ...)
min-size index loop
✅
map(f, lst)
transform lambda (named function or inline lambda)
✅
filter(f, lst)
filter lambda (named function or inline lambda)
✅
input(prompt?)
std::getline
✅
int(x) / float(x) / str(x)
cast / std::stoi / std::to_string
✅
int(s, base)
std::stoi(s, nullptr, base)
✅
list(iterable)
converts set/deque/frozenset → vector; identity if already list
✅
set(iterable)
converts list/frozenset → unordered_set; identity if already set
✅
round(x) / round(x, n)
std::round
✅
pow(x, y)
std::pow
✅
chr(n)
std::string(1, (char)n)
✅
ord(c)
(int)(c[0])
✅
repr(obj)
calls to_string() on user classes
✅
isinstance(x, T)
compile-time / holds_alternative
✅
assert cond, msg
if (!cond) throw
✅
del d[k]
.erase(k)
✅
del x (Optional)
x = std::nullopt
✅
global / nonlocal
no-op (scope access works)
✅
type(x)
❌ requires RTTI
hasattr / getattr / setattr
❌ too dynamic
vars() / dir()
❌ too dynamic
Method
C++
Status
.upper() / .lower()
std::transform
✅
.strip() / .lstrip() / .rstrip()
find first/last non-whitespace
✅
.split(sep)
manual find loop
✅
.split() (whitespace)
std::istringstream
✅
.join(lst)
loop with separator
✅
.replace(old, new)
manual find+replace loop
✅
.find(sub)
.find() + npos → -1
✅
.startswith(s) / .endswith(s)
.compare()
✅
.count(sub)
find loop
✅
.isdigit() / .isalpha() / .isspace() / .isalnum()
std::all_of + ::is*
✅
.isupper() / .islower()
std::all_of + ::is*
✅
.zfill(n)
insert '0'
✅
.ljust(n) / .rjust(n) / .center(n)
pad with fill char
✅
.removeprefix(s) / .removesuffix(s)
compare + substr
✅
.encode()
std::vector<uint8_t> (ASCII/UTF-8)
✅
.decode() on bytes
std::string(begin, end)
✅
f-strings {x}, {x:.2f}, {x:>10d}, {x:.Ne}
std::ostringstream + iomanip
✅
.format(...) (literal receiver only)
same as f-string
✅
Method
C++
Status
.append(x)
.push_back(x)
✅
.pop() / .pop(i)
.pop_back() / erase at index
✅
.insert(i, x)
.insert(begin()+i, x)
✅
.extend(lst)
.insert(end, ...)
✅
.remove(x)
std::find + .erase()
✅
.clear()
.clear()
✅
.reverse()
std::reverse in-place
✅
.sort()
std::sort in-place
✅
.index(x)
std::find → distance
✅
.count(x)
std::count
✅
.copy()
copy constructor
✅
lst1 + lst2
concatenation
✅
lst * n / n * lst
repeat
✅
Method
C++
Status
d[k] read / write
operator[]
✅
.get(k, default) / .get(k)
ternary .count() / std::optional
✅
.setdefault(k, default)
lambda + .count()
✅
.update(other)
loop merge
✅
.pop(k)
.erase(k)
✅
.clear()
.clear()
✅
for k in d / .keys()
structured binding
✅
for v in d.values()
structured binding
✅
for k, v in d.items()
structured binding
✅
.keys() / .values() as list values
lambda → std::vector
✅
k in d / k not in d
.count()
✅
Method
C++
Status
.add(x)
.insert(x)
✅
.remove(x) / .discard(x)
.erase(x)
✅
.clear()
.clear()
✅
.copy()
copy constructor
✅
.union() / .intersection() / .difference() / .symmetric_difference()
lambda
✅
.issubset() / .issuperset() / .isdisjoint()
lambda
✅
s1 | s2, s1 & s2, s1 - s2, s1 ^ s2
operator
✅
s |=, s &=, s -=, s ^=
augmented assign
✅
Method
C++
Status
.append(x) / .appendleft(x)
.push_back / .push_front
✅
.pop() / .popleft()
.pop_back / .pop_front
✅
.extend(lst) / .extendleft(lst)
insert at end / push_front reversed
✅
.remove(x) / .clear()
std::find + erase / .clear()
✅
.rotate(n)
std::rotate
✅
dq[i] subscript
operator[]
✅
.maxlen
❌ std::deque has no max-length; needs custom wrapper
Import system & stdlib stubs
Feature
Status
Multi-file transpilation
✅
Recursive import resolution
✅
from x import y as z aliases
✅
Graceful degradation (skip untranslatable items silently)
✅
Stubs: math, random, time, sys, os
✅
Stubs: typing, dataclasses, collections, enum
✅
Stubs: itertools (chain, product)
✅
Stubs: requests (GET/POST/PUT, Response)
✅
itertools.chain(a, b)
concat lists via lambda
itertools.product(a, b)
nested loops → list[tuple[A,B]]
Third-party packages
❌ requires manual stub file
When a function or class can't be transpiled, Viper emits a // [Viper skip] comment in the C++ output explaining the reason and the source line number (e.g. line 5: missing parameter type annotation). No more silent drops.
Features still not supported:
Feature
Reason
async / await
Requires C++20 coroutines or a scheduler
isinstance() on runtime-polymorphic objects
Requires virtual dispatch / RTTI
for x in heterogeneous tuple
std::tuple is not runtime-iterable
type(x)
Requires RTTI
hasattr / getattr / setattr
Fully dynamic — no static equivalent
vars() / dir()
Requires runtime introspection
__slots__
Irrelevant — Viper declares all fields explicitly
deque.maxlen
std::deque has no capacity limit; needs custom type
Optional[T] self-referential (e.g. linked list node pointing to itself)
std::optional<T> requires complete type; use raw/smart pointer instead
**kwargs with non-string values
Only dict[str, str] (string→string) is supported
Custom decorators
Only @dataclass, @staticmethod, @classmethod, @property, @abstractmethod are handled
Input (example.py)
from typing import Generic , TypeVar
T = TypeVar ('T' )
class Stack (Generic [T ]):
items : list [T ]
def __init__ (self ) -> None :
self .items = []
def push (self , item : T ) -> None :
self .items .append (item )
def pop (self ) -> T :
return self .items .pop ()
def is_empty (self ) -> bool :
return len (self .items ) == 0
if __name__ == "__main__" :
s : Stack [int ] = Stack [int ]()
s .push (1 )
s .push (2 )
s .push (3 )
while not s .is_empty ():
print (s .pop ())
Output (example_out.cpp)
#include < iostream>
#include < string>
#include < vector>
// ...
template <typename T>
class Stack {
public:
std::vector<T> items;
Stack () {}
void push (T item) {
this ->items .push_back (item);
}
T pop () {
return [&]() -> T { auto _t = this ->items .back ();
this ->items .pop_back (); return _t; }();
}
bool is_empty () {
return (int )this ->items .size () == 0 ;
}
};
int main () {
Stack<int > s = Stack<int >();
s.push (1 );
s.push (2 );
s.push (3 );
while (!s.is_empty ()) {
std::cout << s.pop () << std::endl;
}
return 0 ;
}
AGPL v3 . If you use Viper in a network service or distribute it, you must release your source code.
Commercial licenses for proprietary use available upon request — contact mauronofrio (GitHub).