Skip to main content
โšก Calmops

Compile and Execute C++ in One Step: Run C++ Like Go

Working like golang

In Golang, we can use go run main.go to compile and execute the source file in one step. In C++, by default, we need to compile first and then execute.

We can create a shell script to make C++ work like Golang, enabling rapid prototyping and quick testing without manual compilation steps. This guide covers basic scripts, advanced features, watch mode, and modern C++ build tools available in 2026.

Creating the cpprun Script

Step 1: Create a script in a bin directory, named cpprun. For example, /usr/local/bin or ~/bin.

mkdir -p ~/bin
nano ~/bin/cpprun

Step 2: Add the following contents to cpprun.

#!/bin/bash
g++ -std=c++17 -O2 -Wall -Wextra -o /tmp/a.out "$@" && /tmp/a.out

Step 3: Make the script executable.

chmod +x ~/bin/cpprun

Ensure the script is in your PATH:

export PATH="$HOME/bin:$PATH"
# Add to ~/.bashrc for persistence
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc

Step 4: Run the C++ file.

cpprun main.cpp

Enhanced Script with Runtime Arguments

Here’s an improved version that separates compiler arguments from runtime arguments:

#!/bin/bash

# Configuration
CXX="g++"
STD="c++17"
FLAGS="-O2 -Wall -Wextra -Werror"
TMPDIR="/tmp"
BINARY="$TMPDIR/a.out"

# Separate source files from arguments
SOURCES=()
RUNTIME_ARGS=()
ๅŒบๅˆ†_source=1

for arg in "$@"; do
  if [ "$arg" == "--" ]; then
    ๅŒบๅˆ†_source=0
  elif [ $ๅŒบๅˆ†_source -eq 1 ]; then
    SOURCES+=("$arg")
  else
    RUNTIME_ARGS+=("$arg")
  fi
done

# Compile
$CXX $STD $FLAGS -o "$BINARY" "${SOURCES[@]}"

if [ $? -eq 0 ]; then
  # Run with arguments after --
  "$BINARY" "${RUNTIME_ARGS[@]}"
fi

Usage:

cpprun main.cpp -- arg1 arg2

Advanced Script with Multiple Files and Dependencies

For projects with multiple source files and dependencies:

#!/bin/bash
# cpprun-advanced - Advanced C++ runner with multi-file support

set -e

# Configuration
CXX="${CXX:-g++}"
STD="${STD:-c++17}"
OPT="${OPT:-O2}"
WARNINGS="-Wall -Wextra -Wpedantic"
TMPDIR="${TMPDIR:-/tmp}"
BINARY="$TMPDIR/a.out"

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# Usage function
usage() {
    echo "Usage: cpprun-advanced [options] <source files> [-- runtime args]"
    echo "Options:"
    echo "  -c, --compile-only  Only compile, don't run"
    echo "  -o, --output FILE  Output binary name"
    echo "  -d, --debug        Compile with debug symbols (-g)"
    echo "  -v, --verbose      Verbose output"
    echo "  -h, --help         Show this help"
    exit 1
}

# Parse arguments
COMPILE_ONLY=false
OUTPUT_FILE=""
DEBUG=false
VERBOSE=false
SOURCES=()
RUNTIME_ARGS=()
PASS_TO_COMPILER=()

args=("$@")
i=0
while i < ${#args[@]}; do
    case "${args[$i]}" in
        -c|--compile-only)
            COMPILE_ONLY=true
            ;;
        -o|--output)
            i=$((i+1))
            OUTPUT_FILE="${args[$i]}"
            ;;
        -d|--debug)
            DEBUG=true
            ;;
        -v|--verbose)
            VERBOSE=true
            ;;
        -h|--help)
            usage
            ;;
        --)
            # Remaining args are runtime args
            i=$((i+1))
            while i < ${#args[@]}; do
                RUNTIME_ARGS+=("${args[$i]}")
                i=$((i+1))
            done
            break
            ;;
        -*)
            PASS_TO_COMPILER+=("${args[$i]}")
            ;;
        *)
            SOURCES+=("${args[$i]}")
            ;;
    esac
    i=$((i+1))
done

# Validate inputs
if [ ${#SOURCES[@]} -eq 0 ]; then
    echo -e "${RED}Error: No source files provided${NC}"
    usage
fi

# Build compiler command
COMPILE_CMD=($CXX $STD)

# Add debug flags if requested
if [ "$DEBUG" = true ]; then
    COMPILE_CMD+=("-g")
    OPT="Og"  # Better debug experience
fi

# Add optimization and warnings
COMPILE_CMD+=($OPT $WARNINGS)

# Add any extra compiler flags
[ ${#PASS_TO_COMPILER[@]} -gt 0 ] && COMPILE_CMD+=("${PASS_TO_COMPILER[@]}")

# Determine output file
if [ -n "$OUTPUT_FILE" ]; then
    COMPILE_CMD+=("-o" "$OUTPUT_FILE")
else
    COMPILE_CMD+=("-o" "$BINARY")
fi

# Add source files
COMPILE_CMD+=("${SOURCES[@]}")

# Show compilation command if verbose
if [ "$VERBOSE" = true ]; then
    echo -e "${YELLOW}Compiling:${NC} ${COMPILE_CMD[@]}"
fi

# Compile
if "${COMPILE_CMD[@]}"; then
    echo -e "${GREEN}Compilation successful!${NC}"
    
    # Run if not compile-only
    if [ "$COMPILE_ONLY" = false ]; then
        if [ -n "$OUTPUT_FILE" ]; then
            "./$OUTPUT_FILE" "${RUNTIME_ARGS[@]}"
        else
            "$BINARY" "${RUNTIME_ARGS[@]}"
        fi
    fi
else
    echo -e "${RED}Compilation failed!${NC}"
    exit 1
fi

Script Features Explained

  • The script uses g++ to compile the provided C++ file(s) with C++17 standard to a temporary executable /tmp/a.out.
  • If compilation succeeds (&&), it runs the executable.
  • Using /tmp/a.out avoids cluttering the current directory with temporary files.
  • The "$@" allows passing multiple files or additional arguments to g++.
  • -std=c++17 enables modern C++ features
  • -Wall -Wextra enables comprehensive warnings
  • -O2 enables optimization

Important Notes

  • Ensure g++ is installed: sudo apt install g++ (on Ubuntu/Debian) or sudo yum install gcc-c++ (on CentOS).
  • The script assumes the file has a main function.
  • For projects with multiple files or dependencies, consider using Makefiles or build systems like CMake.
  • If you need to pass runtime arguments to the program, use the enhanced version above.
  • Security: Running executables from /tmp is fine for personal use, but be cautious with untrusted code.

Extended Script: Watch Mode

For development, add automatic recompilation:

#!/bin/bash

FILE="$1"
BINARY="/tmp/a.out"

# One-time compilation and run
compile_and_run() {
    g++ -std=c++17 -O2 -Wall -Wextra -o "$BINARY" "$FILE"
    if [ $? -eq 0 ]; then
        "$BINARY" "${@:2}"
    fi
}

# Initial run
compile_and_run "$@"

# Watch mode
echo "Watching for changes... Press Ctrl+C to exit."
while true; do
    sleep 1
    if [ "$FILE" -nt "$BINARY" ]; then
        echo "Recompiling..."
        compile_and_run "$@"
    fi
done

Modern C++ Build Tools (2026)

While shell scripts are great for quick prototyping, modern C++ development benefits from proper build tools:

1. Bazel

Google’s build system:

# Install
brew install bazel  # macOS
# or
sudo apt install bazel  # Ubuntu

# WORKSPACE file
workspace(name = "my_project")

# BUILD file
cc_binary(
    name = "hello",
    srcs = ["hello.cc"],
)

# Build and run
bazel run //:hello

2. CMake with CTest

# CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyApp)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(myapp main.cpp)

enable_testing()
add_test(NAME myapp_test COMMAND myapp)

# Build
mkdir build && cd build
cmake ..
make
ctest

3. Modern CMake with FetchContent

// CMakeLists.txt with dependencies
cmake_minimum_required(VERSION 3.24)
project(MyApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)
FetchContent_Declare(
    json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG v3.11.0
)
FetchContent_MakeAvailable(json)

add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE nlohmann_json::nlohmann_json)

4. C++ Runner Tools

For quick execution without scripts:

# cpp-run (Rust-based)
cargo install cpp-run
cpp-run main.cpp

# clang -run (Clang with -run flag)
clang++ -std=c++20 -stdlib=libc++ -run main.cpp

# Online compilers (for quick testing)
# - Compiler Explorer (godbolt.org)
# - Replit
# - JDoodle

5. Visual Studio Code Tasks

// .vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Build and Run C++",
            "type": "shell",
            "command": "g++",
            "args": [
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}",
                "-std=c++20",
                "-Wall",
                "-Wextra"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": ["$gcc"],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

Example Usage

Create main.cpp:

#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>  // C++20 ranges
#include <numeric> // std::accumulate

int main(int argc, char* argv[]) {
    std::vector<int> nums = {5, 2, 8, 1, 9};
    
    std::cout << "Original: ";
    for (auto n : nums) std::cout << n << " ";
    std::cout << std::endl;
    
    // Using C++20 ranges (if available)
    auto sorted = nums | std::views::transform([](int x) { return x * 2; });
    
    std::sort(nums.begin(), nums.end());
    
    std::cout << "Sorted: ";
    for (auto n : nums) std::cout << n << " ";
    std::cout << std::endl;
    
    // Sum using accumulate
    int sum = std::accumulate(nums.begin(), nums.end(), 0);
    std::cout << "Sum: " << sum << std::endl;
    
    std::cout << "Arguments: ";
    for (int i = 1; i < argc; i++) {
        std::cout << argv[i] << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Run:

cpprun main.cpp hello world

Output:

Original: 5 2 8 1 9 
Sorted: 1 2 5 8 9 
Sum: 25
Arguments: hello world

C++ Standards Progression

Standard Year Key Features
C++11 2011 Smart pointers, lambda, move semantics
C++14 2014 Generic lambdas, constexpr
C++17 2017 std::optional, std::variant, filesystem
C++20 2020 Concepts, ranges, coroutines, modules
C++23 2023 std::print, ranges improvements, deducing this
C++26 2026 Expected, constexpr everything

Best Practices

  1. Use Modern Standards: C++17 or C++20 for new projects
  2. Enable Warnings: Always compile with -Wall -Wextra -Werror
  3. Use Build Systems: CMake, Bazel, or Meson for larger projects
  4. Enable Sanitizers: Catch bugs early with -fsanitize=address,undefined
  5. Use Linters: clang-tidy for static analysis
# Compile with sanitizers
g++ -std=c++20 -fsanitize=address,undefined -g main.cpp -o main

References

Comments