⚡ C++ for TypeScript developers
21 Jan, 2026
On this page
A guide to C++ for developers coming from TypeScript/JavaScript.
Key differences from TypeScript Jump to heading
| Concept | TypeScript | C++ |
|---|---|---|
| Typing | Static, structural | Static, nominal |
| Execution | Interpreted (via JS engine) | Compiled to native binary |
| Memory | Garbage collected | Manual (or smart pointers) |
| Null | null / undefined |
nullptr, uninitialized |
| Package manager | npm/yarn/pnpm | vcpkg, Conan, CMake |
| Build system | Bundlers (Webpack, etc.) | CMake, Make, Ninja |
Variables and constants Jump to heading
TypeScript Jump to heading
const name: string = "Alice"
let age: number = 30
const isActive = true
C++ Jump to heading
#include <string>
// Variables
std::string name = "Alice"; // std::string, not primitive
int age = 30;
bool isActive = true;
double price = 19.99;
// const (compile-time or runtime constant)
const int MAX_SIZE = 100;
const std::string PREFIX = "user_";
// constexpr (compile-time constant, preferred)
constexpr int BUFFER_SIZE = 1024;
// auto (type inference, like TypeScript's inference)
auto count = 42; // int
auto message = "hello"; // const char*
auto name = std::string("Alice"); // std::string
Basic types Jump to heading
| TypeScript | C++ |
|---|---|
string |
std::string |
number |
int, long, float, double |
boolean |
bool |
any |
No direct equivalent (use templates/variants) |
null |
nullptr (for pointers) |
Array<T> |
std::vector<T> |
Record<K, V> |
std::map<K, V> or std::unordered_map<K, V> |
Set<T> |
std::set<T> or std::unordered_set<T> |
[T, U] |
std::tuple<T, U> or std::pair<T, U> |
Numeric types Jump to heading
// Integers
int x = 42; // At least 16 bits
long y = 100000L; // At least 32 bits
long long z = 10000000000LL; // At least 64 bits
// Fixed-width integers (preferred)
#include <cstdint>
int32_t a = 42;
int64_t b = 100000;
uint32_t c = 42; // Unsigned
// Floating point
float f = 3.14f;
double d = 3.14159265359;
// Size in bytes
sizeof(int); // Platform dependent (usually 4)
sizeof(double); // Usually 8
Functions Jump to heading
TypeScript Jump to heading
function greet(name: string): string {
return `Hello, ${name}!`
}
const add = (a: number, b: number): number => a + b
function log(message: string, level: string = "INFO"): void {
console.log(`[${level}] ${message}`)
}
C++ Jump to heading
#include <string>
#include <iostream>
// Basic function
std::string greet(const std::string& name) {
return "Hello, " + name + "!";
}
// Lambda (like arrow functions)
auto add = [](int a, int b) -> int {
return a + b;
};
// Short form
auto add = [](int a, int b) { return a + b; };
// Default parameters
void log(const std::string& message, const std::string& level = "INFO") {
std::cout << "[" << level << "] " << message << "\n";
}
// Function overloading (multiple functions, same name)
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
std::string add(const std::string& a, const std::string& b) { return a + b; }
// Pass by value, reference, or pointer
void byValue(int x); // Copy
void byReference(int& x); // Mutable reference
void byConstRef(const int& x); // Read-only reference
void byPointer(int* x); // Pointer (can be null)
Lambda captures Jump to heading
int factor = 2;
// Capture by value (copy)
auto multiply = [factor](int n) { return n * factor; };
// Capture by reference
auto increment = [&factor](int n) { factor++; return n * factor; };
// Capture all by value
auto fn1 = [=]() { return factor; };
// Capture all by reference
auto fn2 = [&]() { factor++; };
// Mixed
auto fn3 = [=, &factor]() { /* ... */ };
Classes Jump to heading
TypeScript Jump to heading
class User {
private id: number
public name: string
readonly email: string
constructor(id: number, name: string, email: string) {
this.id = id
this.name = name
this.email = email
}
greet(): string {
return `Hello, ${this.name}`
}
static create(name: string): User {
return new User(Date.now(), name, "")
}
}
C++ Jump to heading
#include <string>
#include <ctime>
class User {
private:
int id;
public:
std::string name;
const std::string email; // const = readonly (set once in constructor)
// Constructor with initializer list
User(int id, const std::string& name, const std::string& email)
: id(id), name(name), email(email) {}
// Method
std::string greet() const { // const = doesn't modify object
return "Hello, " + name;
}
// Getter
int getId() const { return id; }
// Static method
static User create(const std::string& name) {
return User(static_cast<int>(std::time(nullptr)), name, "");
}
};
// Usage
User user(1, "Alice", "alice@example.com");
user.greet();
User::create("Bob");
Structs (like public-by-default classes) Jump to heading
// struct = class with public default access
// Use for simple data containers (like TypeScript interfaces)
struct Point {
double x;
double y;
double distance() const {
return std::sqrt(x * x + y * y);
}
};
Point p{3.0, 4.0}; // Aggregate initialization
p.x = 5.0;
Inheritance and polymorphism Jump to heading
TypeScript Jump to heading
interface Drawable {
draw(): void
}
class Shape {
protected x: number
constructor(x: number) { this.x = x }
}
class Circle extends Shape implements Drawable {
constructor(x: number, public radius: number) {
super(x)
}
draw() { /* ... */ }
}
C++ Jump to heading
// Abstract base class (like interface)
class Drawable {
public:
virtual void draw() const = 0; // Pure virtual = abstract
virtual ~Drawable() = default; // Virtual destructor
};
class Shape {
protected:
double x;
public:
Shape(double x) : x(x) {}
virtual ~Shape() = default;
};
// Multiple inheritance
class Circle : public Shape, public Drawable {
private:
double radius;
public:
Circle(double x, double radius) : Shape(x), radius(radius) {}
void draw() const override {
// Implementation
}
};
// Usage
Circle circle(0, 5.0);
Drawable* drawable = &circle; // Polymorphism via pointer
drawable->draw();
// Or with smart pointers (preferred)
std::unique_ptr<Drawable> shape = std::make_unique<Circle>(0, 5.0);
shape->draw();
Memory management Jump to heading
C++ requires explicit memory management. Modern C++ uses smart pointers.
TypeScript Jump to heading
const user = new User() // Garbage collected automatically
C++ Jump to heading
#include <memory>
// Stack allocation (automatic cleanup)
User user(1, "Alice", ""); // Destroyed when scope ends
// Heap allocation (manual - AVOID in modern C++)
User* ptr = new User(1, "Alice", "");
delete ptr; // Must manually delete!
// Smart pointers (modern C++, preferred)
// unique_ptr: Single owner, auto-deleted
std::unique_ptr<User> user1 = std::make_unique<User>(1, "Alice", "");
// No delete needed - automatic when out of scope
// shared_ptr: Multiple owners, reference counted
std::shared_ptr<User> user2 = std::make_shared<User>(1, "Alice", "");
std::shared_ptr<User> user3 = user2; // Both point to same object
// Deleted when last shared_ptr is destroyed
// weak_ptr: Non-owning reference (breaks cycles)
std::weak_ptr<User> weak = user2;
if (auto locked = weak.lock()) { // Check if still valid
locked->greet();
}
Vectors (dynamic arrays) Jump to heading
TypeScript Jump to heading
const arr: number[] = [1, 2, 3]
arr.push(4)
const doubled = arr.map(n => n * 2)
const evens = arr.filter(n => n % 2 === 0)
C++ Jump to heading
#include <vector>
#include <algorithm>
std::vector<int> arr = {1, 2, 3};
arr.push_back(4);
// Size and access
arr.size(); // 4
arr[0]; // 1 (no bounds checking)
arr.at(0); // 1 (throws if out of bounds)
arr.front(); // First element
arr.back(); // Last element
// Iteration
for (int n : arr) {
std::cout << n << "\n";
}
for (const auto& n : arr) { // By reference (more efficient)
std::cout << n << "\n";
}
// Transform (like map)
std::vector<int> doubled;
std::transform(arr.begin(), arr.end(), std::back_inserter(doubled),
[](int n) { return n * 2; });
// Filter (copy_if)
std::vector<int> evens;
std::copy_if(arr.begin(), arr.end(), std::back_inserter(evens),
[](int n) { return n % 2 == 0; });
// C++20 ranges (more like JS)
#include <ranges>
auto doubled = arr | std::views::transform([](int n) { return n * 2; });
auto evens = arr | std::views::filter([](int n) { return n % 2 == 0; });
// Reduce (accumulate)
#include <numeric>
int sum = std::accumulate(arr.begin(), arr.end(), 0);
Maps Jump to heading
TypeScript Jump to heading
const scores: Record<string, number> = {
alice: 100,
bob: 85
}
scores["charlie"] = 90
C++ Jump to heading
#include <map>
#include <unordered_map>
#include <string>
// Ordered map (sorted by key, uses tree)
std::map<std::string, int> scores = {
{"alice", 100},
{"bob", 85}
};
// Unordered map (faster, uses hash table, like JS object)
std::unordered_map<std::string, int> scores = {
{"alice", 100},
{"bob", 85}
};
// Insert/update
scores["charlie"] = 90;
scores.insert({"david", 70});
// Access
int score = scores["alice"]; // Creates entry if missing!
int score = scores.at("alice"); // Throws if missing
// Check if key exists
if (scores.count("alice") > 0) { /* exists */ }
if (scores.contains("alice")) { /* C++20 */ }
// Find
auto it = scores.find("alice");
if (it != scores.end()) {
std::cout << it->second << "\n"; // 100
}
// Delete
scores.erase("bob");
// Iterate
for (const auto& [name, score] : scores) { // Structured binding
std::cout << name << ": " << score << "\n";
}
Error handling Jump to heading
TypeScript Jump to heading
try {
const data = await fetchData()
} catch (error) {
if (error instanceof NotFoundError) {
console.log("Not found")
}
throw error
}
C++ Jump to heading
#include <stdexcept>
try {
auto data = fetchData();
} catch (const NotFoundException& e) {
std::cerr << "Not found: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
throw; // Re-throw
} catch (...) {
std::cerr << "Unknown error\n";
}
// Custom exception
class ValidationError : public std::runtime_error {
public:
std::string field;
ValidationError(const std::string& field, const std::string& message)
: std::runtime_error(field + ": " + message), field(field) {}
};
throw ValidationError("email", "Invalid format");
// Modern C++: std::optional for "might not have value"
#include <optional>
std::optional<User> findUser(int id) {
if (id <= 0) return std::nullopt;
return User(id, "Alice", "");
}
if (auto user = findUser(1)) {
user->greet(); // Use like pointer
}
// std::expected (C++23) for Result type
#include <expected>
std::expected<User, std::string> findUser(int id) {
if (id <= 0) return std::unexpected("Invalid ID");
return User(id, "Alice", "");
}
Templates (generics) Jump to heading
TypeScript Jump to heading
function first<T>(arr: T[]): T | undefined {
return arr[0]
}
interface Container<T> {
value: T
map<U>(fn: (v: T) => U): Container<U>
}
C++ Jump to heading
// Function template
template<typename T>
T first(const std::vector<T>& arr) {
if (arr.empty()) throw std::runtime_error("Empty vector");
return arr[0];
}
// With optional
template<typename T>
std::optional<T> first(const std::vector<T>& arr) {
if (arr.empty()) return std::nullopt;
return arr[0];
}
// Class template
template<typename T>
class Container {
private:
T value;
public:
Container(T v) : value(v) {}
T get() const { return value; }
template<typename U>
Container<U> map(std::function<U(T)> fn) const {
return Container<U>(fn(value));
}
};
// Usage
Container<int> c(42);
auto doubled = c.map<int>([](int n) { return n * 2; });
// Concepts (C++20) - like type constraints
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<Numeric T>
T sum(const std::vector<T>& nums) {
return std::accumulate(nums.begin(), nums.end(), T{});
}
Strings Jump to heading
TypeScript Jump to heading
const name = "Alice"
const message = `Hello, ${name}!`
"hello".toUpperCase()
C++ Jump to heading
#include <string>
#include <sstream>
#include <algorithm>
#include <format> // C++20
std::string name = "Alice";
// Concatenation
std::string message = "Hello, " + name + "!";
// String formatting (C++20)
std::string message = std::format("Hello, {}!", name);
// Older: stringstream
std::ostringstream ss;
ss << "Hello, " << name << "! You have " << 5 << " messages.";
std::string result = ss.str();
// String operations
name.length(); // or .size()
name.substr(0, 3); // "Ali"
name.find("li"); // 1 (position)
name.empty(); // false
name.starts_with("Al"); // C++20
name.ends_with("ce"); // C++20
// Transform (no built-in toUpper on string)
std::string upper = name;
std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);
// C-style string (const char*)
const char* cstr = name.c_str();
// String to number
int n = std::stoi("42");
double d = std::stod("3.14");
// Number to string
std::string s = std::to_string(42);
File I/O Jump to heading
TypeScript (Node.js) Jump to heading
import { readFileSync, writeFileSync } from 'fs'
const content = readFileSync('file.txt', 'utf-8')
writeFileSync('output.txt', 'Hello, World!')
C++ Jump to heading
#include <fstream>
#include <string>
#include <sstream>
// Read entire file
std::ifstream file("file.txt");
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();
// Read line by line
std::ifstream file("file.txt");
std::string line;
while (std::getline(file, line)) {
std::cout << line << "\n";
}
// Write to file
std::ofstream out("output.txt");
out << "Hello, World!\n";
out.close();
// Append
std::ofstream out("output.txt", std::ios::app);
out << "More content\n";
// Check if file opened
if (!file.is_open()) {
throw std::runtime_error("Could not open file");
}
// C++17: std::filesystem
#include <filesystem>
namespace fs = std::filesystem;
if (fs::exists("file.txt")) {
auto size = fs::file_size("file.txt");
}
for (const auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path() << "\n";
}
JSON handling Jump to heading
C++ has no built-in JSON. Use a library like nlohmann/json.
TypeScript Jump to heading
const json = JSON.stringify(obj)
const parsed = JSON.parse(json)
C++ (nlohmann/json) Jump to heading
#include <nlohmann/json.hpp>
using json = nlohmann::json;
// Create JSON
json j;
j["name"] = "Alice";
j["age"] = 30;
j["tags"] = {"admin", "user"};
// From object
struct User {
std::string name;
int age;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, name, age) // Macro for serialization
User user{"Alice", 30};
json j = user;
// Stringify
std::string str = j.dump(); // Compact
std::string str = j.dump(2); // Pretty with 2-space indent
// Parse
json parsed = json::parse(str);
std::string name = parsed["name"];
int age = parsed["age"].get<int>();
// To object
User user = parsed.get<User>();
// Safe access
if (parsed.contains("email")) {
auto email = parsed["email"];
}
auto email = parsed.value("email", "default@example.com");
Concurrency Jump to heading
TypeScript Jump to heading
async function fetchAll(urls: string[]): Promise<Response[]> {
return Promise.all(urls.map(url => fetch(url)))
}
C++ Jump to heading
#include <thread>
#include <future>
#include <vector>
#include <mutex>
// Basic thread
std::thread t([]() {
std::cout << "Hello from thread\n";
});
t.join(); // Wait for completion
// std::async (like Promise)
std::future<int> result = std::async(std::launch::async, []() {
return computeValue();
});
int value = result.get(); // Blocks until ready
// Multiple async tasks (like Promise.all)
std::vector<std::future<std::string>> futures;
for (const auto& url : urls) {
futures.push_back(std::async(std::launch::async, [&url]() {
return fetch(url);
}));
}
std::vector<std::string> results;
for (auto& f : futures) {
results.push_back(f.get());
}
// Mutex for thread safety
std::mutex mtx;
int counter = 0;
void increment() {
std::lock_guard<std::mutex> lock(mtx); // RAII lock
counter++;
}
// C++20: std::jthread (auto-joining)
std::jthread t([]() {
// work
});
// No need to call join()
Build system (CMake) Jump to heading
TypeScript Jump to heading
{
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
}
}
C++ (CMake) Jump to heading
# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Add executable
add_executable(myapp
src/main.cpp
src/utils.cpp
)
# Include directories
target_include_directories(myapp PRIVATE include)
# Link libraries
find_package(nlohmann_json REQUIRED)
target_link_libraries(myapp PRIVATE nlohmann_json::nlohmann_json)
# Add tests
enable_testing()
add_executable(tests tests/test_main.cpp)
add_test(NAME MyTests COMMAND tests)
# Build
mkdir build && cd build
cmake ..
cmake --build .
# Run
./myapp
Testing (Catch2) Jump to heading
TypeScript (Jest) Jump to heading
describe('math', () => {
it('adds numbers', () => {
expect(add(1, 2)).toBe(3)
})
})
C++ (Catch2) Jump to heading
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
TEST_CASE("Math operations", "[math]") {
SECTION("addition") {
REQUIRE(add(1, 2) == 3);
REQUIRE(add(-1, 1) == 0);
}
SECTION("division") {
REQUIRE(divide(10, 2) == 5);
REQUIRE_THROWS_AS(divide(1, 0), std::runtime_error);
}
}
// Parameterized tests
TEST_CASE("Addition with parameters") {
auto [a, b, expected] = GENERATE(table<int, int, int>({
{1, 2, 3},
{0, 0, 0},
{-1, 1, 0}
}));
REQUIRE(add(a, b) == expected);
}
Common gotchas for TS developers Jump to heading
- Compilation required - no REPL-like experience
- Memory management - use smart pointers, avoid raw
new/delete - No garbage collection - resources must be released
- Headers vs source - declarations in
.h, definitions in.cpp #includeis text substitution - use include guards- Pass by value copies - use
const&for efficiency - Undefined behavior - accessing out-of-bounds, null pointers, etc.
- Iterators not indices - STL uses iterator pattern
std::stringis notconst char*- two different types- No reflection - no runtime type info like TypeScript
// Include guard
#ifndef MY_CLASS_H
#define MY_CLASS_H
class MyClass {
// ...
};
#endif
// Or modern (most compilers)
#pragma once
class MyClass {
// ...
};
Resources Jump to heading
← Back home