Convert C code to idiomatic C++. Use when migrating C projects to C++, translating C patterns to modern C++ idioms, or refactoring C codebases into C++. Extends meta-convert-dev with C-to-C++ specific patterns covering all 8 pillars (Module, Error, Concurrency, Metaprogramming, Zero/Default, Serialization, Build, Testing).
Instrucciones de origen · Vista previa de solo lectura
name
convert-c-cpp
description
Convert C code to idiomatic C++. Use when migrating C projects to C++, translating C patterns to modern C++ idioms, or refactoring C codebases into C++. Extends meta-convert-dev with C-to-C++ specific patterns covering all 8 pillars (Module, Error, Concurrency, Metaprogramming, Zero/Default, Serialization, Build, Testing).
Convert C to C++
Convert C code to idiomatic modern C++. This skill extends meta-convert-dev with C-to-C++ specific type mappings, idiom translations, and tooling guidance.
Adopt C++ idioms - Don't write "C code with cout"; use modern C++ patterns
Use standard library - Replace custom implementations with STL containers/algorithms
Test incrementally - Convert module by module, ensuring tests pass
Enable compiler warnings - Use -Wall -Wextra -Wpedantic to catch issues
Type System Mapping
Primitive Types
C
C++
Notes
char, int, long
Same
C++ inherits C primitive types
unsigned int
Same or size_t
Prefer size_t for sizes/indices
int8_t, uint8_t
Same (<cstdint>)
Exact-width integers
NULL
nullptr
Type-safe null pointer constant
void*
Avoid
Use templates or std::any instead
bool (C99)
bool
Native in C++, requires <stdbool.h> in C
Collection Types
C
C++
Notes
int arr[10]
std::array<int, 10>
Fixed-size, bounds-checked
int* arr = malloc(...)
std::vector<int>
Dynamic, RAII, automatic resize
Linked list (manual)
std::list<T>, std::forward_list<T>
Standard library
Hash table (manual)
std::unordered_map<K, V>
Efficient lookup
Binary tree (manual)
std::map<K, V>, std::set<T>
Ordered containers
Composite Types
C
C++
Notes
struct Point { int x, y; };
struct Point { int x, y; };
Same, but struct keyword optional for instances
typedef struct { ... } Name;
struct Name { ... };
Implicit typedef in C++
union Data { ... }
std::variant<...>
Type-safe tagged union
enum { A, B, C }
enum class Status { A, B, C }
Scoped, strongly-typed
Tagged union (manual)
std::variant<...>
Type-safe alternative
Function Types
C
C++
Notes
int (*func_ptr)(int, int)
std::function<int(int, int)>
Type-erased, can hold lambdas
typedef int (*Callback)(void*)
std::function<int(void*)>
Modern function objects
void qsort(void*, size_t, ...)
std::sort(begin, end, comparator)
Type-safe, no void*
Idiom Translation
Pattern 1: Memory Management (malloc/free → RAII)
C:
#include <stdlib.h>
int* create_array(size_t n) {
int* arr = malloc(n * sizeof(int));
if (arr == NULL) {
return NULL;
}
for (size_t i = 0; i < n; i++) {
arr[i] = i * 2;
}
return arr;
}
void process() {
int* data = create_array(100);
if (data == NULL) {
return; // Error handling
}
// Use data...
free(data); // Manual cleanup
}
C++:
#include <vector>
std::vector<int> create_array(size_t n) {
std::vector<int> arr(n);
for (size_t i = 0; i < n; i++) {
arr[i] = i * 2;
}
return arr; // Move semantics, no copy
}
void process() {
auto data = create_array(100);
// Use data...
// Automatically freed when data goes out of scope
}
Why this translation:
std::vector manages memory automatically (RAII)
No risk of memory leaks or use-after-free
Return by value is efficient with move semantics
Bounds checking available with .at(i) instead of []
Pattern 6: Function Pointers → Lambdas/std::function
C:
typedef int (*Comparator)(const void*, const void*);
int int_compare(const void* a, const void* b) {
int ia = *(const int*)a;
int ib = *(const int*)b;
return ia - ib;
}
void sort_array(int* arr, size_t n, Comparator cmp) {
qsort(arr, n, sizeof(int), cmp);
}
// Usage
int data[] = {5, 2, 8, 1, 9};
sort_array(data, 5, int_compare);
C++:
#include <algorithm>
#include <vector>
// Type-safe, no void*
void sort_array(std::vector<int>& arr, auto comparator) {
std::sort(arr.begin(), arr.end(), comparator);
}
// Usage with lambda
std::vector<int> data = {5, 2, 8, 1, 9};
std::sort(data.begin(), data.end(), [](int a, int b) {
return a < b;
});
// Or reverse sort
std::sort(data.begin(), data.end(), [](int a, int b) {
return a > b;
});
Why this translation:
Lambdas are type-safe (no void* casting)
Can capture local variables
Inline definition for simple comparisons
std::sort is faster than qsort (inlined, type-specific)
// Type-safe templates
template<typename T>
constexpr T max(T a, T b) {
return (a > b) ? a : b;
}
template<typename T>
constexpr T square(T x) {
return x * x;
}
template<typename T, size_t N>
constexpr size_t array_size(T (&)[N]) {
return N;
}
// Or use C++17 std::size
#include <iterator>
int arr[] = {1, 2, 3, 4, 5};
size_t size = std::size(arr);
// Swap with template
template<typename T>
void swap(T& a, T& b) {
T temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}
// Or just use std::swap
#include <utility>
std::swap(a, b);
Why this translation:
Templates provide type safety
constexpr enables compile-time evaluation
Standard library provides std::swap, std::max, std::min
Better error messages than macro errors
Debugger-friendly (macros are invisible after preprocessing)
Pattern 8: Enums → Scoped Enums
C:
enum Color {
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE
};
enum Status {
STATUS_OK,
STATUS_ERROR
};
// Name conflicts possible
int color = COLOR_RED;
C++:
enum class Color {
Red,
Green,
Blue
};
enum class Status {
Ok,
Error
};
// No name conflicts, must scope
Color color = Color::Red;
Status status = Status::Ok;
// Stronger type safety
// Color c = Status::Ok; // Error: type mismatch
Why this translation:
enum class prevents name conflicts (scoped)
No implicit conversion to int
Stronger type safety
Explicit scoping improves readability
Error Handling Translation
C Error Model → C++ Error Models
C Pattern
C++ Pattern
When to Use
Return code (int)
std::optional<T>
Simple success/failure, no error details needed
Return code + errno
Exceptions
Rare errors, rich error context
Return code + output param
std::expected<T, E> (C++23)
Error details needed, exceptions undesirable
NULL return
std::optional<T>
May or may not find a value
Error Code → std::optional
C:
#define SUCCESS 0
#define ERROR_NOT_FOUND -1
int get_config_value(const char* key, int* out_value) {
if (key == NULL || out_value == NULL) {
return -1;
}
// Lookup logic
if (/* not found */) {
return ERROR_NOT_FOUND;
}
*out_value = /* found value */;
return SUCCESS;
}
C++:
std::optional<int> get_config_value(const std::string& key) {
// Lookup logic
if (/* not found */) {
return std::nullopt;
}
return /* found value */;
}
// Usage
if (auto value = get_config_value("timeout")) {
std::cout << "Timeout: " << *value << '\n';
} else {
std::cout << "Key not found\n";
}
Error Code → Exceptions
C:
int open_database(const char* path, Database** out_db) {
if (path == NULL || out_db == NULL) {
return ERROR_INVALID_ARG;
}
Database* db = malloc(sizeof(Database));
if (db == NULL) {
return ERROR_OUT_OF_MEMORY;
}
if (/* connection failed */) {
free(db);
return ERROR_CONNECTION_FAILED;
}
*out_db = db;
return SUCCESS;
}
// Caller must check every error
int result = open_database(path, &db);
if (result != SUCCESS) {
// Handle specific errors
}
C++:
#include <stdexcept>
#include <memory>
class Database {
public:
Database(const std::string& path) {
if (/* connection failed */) {
throw std::runtime_error("Failed to connect to database");
}
// Initialize
}
// RAII: destructor closes connection
~Database() {
// Close connection
}
};
// Usage - exceptions propagate automatically
try {
Database db(path);
// Use db
} catch (const std::runtime_error& e) {
std::cerr << "Error: " << e.what() << '\n';
}
Why this translation:
Exceptions separate error handling from main logic
#include <memory>
class LargeObject {
// ... large data ...
};
// Unique ownership
auto obj = std::make_unique<LargeObject>();
// ... use obj ...
// Automatically deleted when obj goes out of scope
// Shared ownership
auto shared = std::make_shared<LargeObject>();
auto copy = shared; // Reference count = 2
// Deleted when last shared_ptr is destroyed
Why this translation:
No manual memory management needed
Impossible to forget cleanup
Exception-safe (cleanup happens even if exception thrown)
Clear ownership semantics
Concurrency Translation
pthreads → std::thread and Synchronization Primitives
// point.hpp
#pragma once // Modern alternative to include guards
namespace geometry {
class Point {
public:
Point(double x, double y);
double distance(const Point& other) const;
private:
double x, y;
};
} // namespace geometry