Boost.Program_options

A powerful program option processing library provided as part of the Boost libraries.

cppclicommand-lineboost

Framework

Boost.Program_options

Overview

Boost.Program_options is a powerful program option processing library provided as part of the Boost libraries. It has been used for years as part of the Boost ecosystem with extensive track record in large projects. It features unified handling of options from command-line arguments, configuration files, and environment variables.

Details

Boost.Program_options is a full-featured library for option processing in C++ programs. It can read settings not only from command-line arguments but also from configuration files and environment variables, managing them in an integrated manner. Developed based on Boost library's high quality standards, it is widely used in industrial-level applications.

Key Features

  • Multiple Input Sources: Integrated processing of command-line, configuration files, and environment variables
  • Rich Configuration File Formats: Standard support for INI format, custom formats also possible
  • Type Safe: Safe option processing utilizing C++ type system
  • Validation: Built-in option value validation functionality
  • Internationalization Support: Unicode string support
  • Error Handling: Detailed and clear error messages
  • Extensibility: Custom option processing implementation possible
  • Boost Quality: High reliability and performance

Pros and Cons

Pros

  • Integrated Configuration Management: Unified processing of command-line, configuration files, and environment variables
  • High Stability: Long track record as part of Boost libraries
  • Rich Features: Provides advanced option processing functionality
  • Configuration File Support: Standard support for INI format and other configuration files
  • Extensibility: Easy customization and high flexibility
  • Documentation: Comprehensive official documentation
  • Platform Support: Works on a wide range of platforms

Cons

  • Dependencies: Requires dependency on entire Boost library (partial usage possible)
  • Learning Curve: Takes time to master due to rich features
  • Library Size: Heavy for cases requiring lightweight solutions
  • Compile Time: Increased compilation time due to template usage characteristic of Boost libraries

Key Links

Usage Examples

Basic Usage

#include <boost/program_options.hpp>
#include <iostream>
#include <fstream>

namespace po = boost::program_options;

int main(int argc, char* argv[]) {
    try {
        po::options_description desc("Available options");
        desc.add_options()
            ("help,h", "Show help message")
            ("input-file,i", po::value<std::string>(), "Input file name")
            ("output-file,o", po::value<std::string>(), "Output file name")
            ("verbose,v", "Enable verbose output")
            ("compression", po::value<int>()->default_value(10), "Compression level (1-10)");

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);

        if (vm.count("help")) {
            std::cout << desc << std::endl;
            return 0;
        }

        if (vm.count("input-file")) {
            std::cout << "Input file: " << vm["input-file"].as<std::string>() << std::endl;
        } else {
            std::cout << "Input file not specified" << std::endl;
            return 1;
        }

        if (vm.count("output-file")) {
            std::cout << "Output file: " << vm["output-file"].as<std::string>() << std::endl;
        }

        if (vm.count("verbose")) {
            std::cout << "Verbose mode enabled" << std::endl;
        }

        std::cout << "Compression level: " << vm["compression"].as<int>() << std::endl;

    } catch(std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

Configuration File Support

#include <boost/program_options.hpp>
#include <iostream>
#include <fstream>

namespace po = boost::program_options;

int main(int argc, char* argv[]) {
    try {
        // Command-line only options
        po::options_description generic("General options");
        generic.add_options()
            ("version,v", "Show version information")
            ("help,h", "Show help message")
            ("config,c", po::value<std::string>()->default_value("config.ini"), 
             "Configuration file name");

        // Options available in config file
        po::options_description config("Configuration");
        config.add_options()
            ("host", po::value<std::string>()->default_value("localhost"), 
             "Host to connect to")
            ("port", po::value<int>()->default_value(8080), 
             "Port number")
            ("timeout", po::value<int>()->default_value(30), 
             "Timeout (seconds)")
            ("max-connections", po::value<int>()->default_value(100), 
             "Maximum connections")
            ("log-level", po::value<std::string>()->default_value("info"), 
             "Log level")
            ("ssl-enabled", po::value<bool>()->default_value(false), 
             "Enable SSL connection");

        // For command-line display
        po::options_description cmdline_options;
        cmdline_options.add(generic).add(config);

        // For config file
        po::options_description config_file_options;
        config_file_options.add(config);

        po::variables_map vm;
        
        // Parse command-line arguments
        po::store(po::parse_command_line(argc, argv, cmdline_options), vm);
        po::notify(vm);

        if (vm.count("help")) {
            std::cout << cmdline_options << std::endl;
            return 0;
        }

        if (vm.count("version")) {
            std::cout << "Version 1.0.0" << std::endl;
            return 0;
        }

        // Load configuration file
        std::string config_file = vm["config"].as<std::string>();
        std::ifstream ifs(config_file);
        if (ifs) {
            std::cout << "Loading configuration file '" << config_file << "'..." << std::endl;
            po::store(po::parse_config_file(ifs, config_file_options), vm);
            po::notify(vm);
        } else {
            std::cout << "Configuration file '" << config_file << "' not found. Using default values." << std::endl;
        }

        // Display configuration values
        std::cout << "Server configuration:" << std::endl;
        std::cout << "  Host: " << vm["host"].as<std::string>() << std::endl;
        std::cout << "  Port: " << vm["port"].as<int>() << std::endl;
        std::cout << "  Timeout: " << vm["timeout"].as<int>() << " seconds" << std::endl;
        std::cout << "  Max connections: " << vm["max-connections"].as<int>() << std::endl;
        std::cout << "  Log level: " << vm["log-level"].as<std::string>() << std::endl;
        std::cout << "  SSL: " << (vm["ssl-enabled"].as<bool>() ? "enabled" : "disabled") << std::endl;

    } catch(std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

Positional Arguments and Multiple Values

#include <boost/program_options.hpp>
#include <iostream>
#include <vector>

namespace po = boost::program_options;

int main(int argc, char* argv[]) {
    try {
        po::options_description desc("File processing tool");
        desc.add_options()
            ("help,h", "Show help message")
            ("files", po::value<std::vector<std::string>>()->multitoken(), 
             "Files to process")
            ("include,I", po::value<std::vector<std::string>>()->composing(), 
             "Include directories")
            ("define,D", po::value<std::vector<std::string>>()->composing(), 
             "Macro definitions")
            ("output-dir,o", po::value<std::string>()->default_value("./output"), 
             "Output directory")
            ("jobs,j", po::value<int>()->default_value(1), 
             "Number of parallel jobs")
            ("recursive,r", "Process recursively")
            ("dry-run,n", "Show what would be done without actually executing");

        // Positional arguments setup
        po::positional_options_description p;
        p.add("files", -1);  // Unlimited positional arguments

        po::variables_map vm;
        po::store(po::command_line_parser(argc, argv)
                    .options(desc)
                    .positional(p)
                    .run(), vm);
        po::notify(vm);

        if (vm.count("help")) {
            std::cout << "Usage: " << argv[0] << " [options] file1 file2 ..." << std::endl;
            std::cout << desc << std::endl;
            return 0;
        }

        if (!vm.count("files")) {
            std::cerr << "Error: No files specified for processing" << std::endl;
            return 1;
        }

        auto files = vm["files"].as<std::vector<std::string>>();
        std::cout << "Files to process:" << std::endl;
        for (const auto& file : files) {
            std::cout << "  " << file << std::endl;
        }

        if (vm.count("include")) {
            auto includes = vm["include"].as<std::vector<std::string>>();
            std::cout << "Include directories:" << std::endl;
            for (const auto& inc : includes) {
                std::cout << "  " << inc << std::endl;
            }
        }

        if (vm.count("define")) {
            auto defines = vm["define"].as<std::vector<std::string>>();
            std::cout << "Macro definitions:" << std::endl;
            for (const auto& def : defines) {
                std::cout << "  " << def << std::endl;
            }
        }

        std::cout << "Output directory: " << vm["output-dir"].as<std::string>() << std::endl;
        std::cout << "Parallel jobs: " << vm["jobs"].as<int>() << std::endl;
        std::cout << "Recursive processing: " << (vm.count("recursive") ? "enabled" : "disabled") << std::endl;
        std::cout << "Dry run: " << (vm.count("dry-run") ? "enabled" : "disabled") << std::endl;

    } catch(std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

Environment Variables and Custom Validation

#include <boost/program_options.hpp>
#include <iostream>
#include <regex>

namespace po = boost::program_options;

// Custom validation function
void validate(boost::any& v, const std::vector<std::string>& values,
              std::string* target_type, int) {
    po::validators::check_first_occurrence(v, values, target_type);
    const std::string& s = po::validators::get_single_string(values);
    
    // Email address validation
    std::regex email_pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
    if (!std::regex_match(s, email_pattern)) {
        throw po::validation_error(po::validation_error::invalid_option_value);
    }
    
    v = boost::any(s);
}

struct email_address {
    std::string addr;
};

void validate(boost::any& v, const std::vector<std::string>& values,
              email_address* target_type, int) {
    po::validators::check_first_occurrence(v, values, target_type);
    const std::string& s = po::validators::get_single_string(values);
    
    std::regex email_pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
    if (!std::regex_match(s, email_pattern)) {
        throw po::validation_error(po::validation_error::invalid_option_value);
    }
    
    v = boost::any(email_address{s});
}

int main(int argc, char* argv[]) {
    try {
        po::options_description desc("Application configuration");
        desc.add_options()
            ("help,h", "Show help message")
            ("database-url", po::value<std::string>(), 
             "Database connection URL (env var: DATABASE_URL)")
            ("admin-email", po::value<email_address>(), 
             "Administrator email address")
            ("max-workers", po::value<int>()->default_value(4), 
             "Maximum number of workers (env var: MAX_WORKERS)")
            ("debug-mode", po::value<bool>()->default_value(false), 
             "Debug mode (env var: DEBUG_MODE)")
            ("api-key", po::value<std::string>(), 
             "API key (env var: API_KEY)");

        po::variables_map vm;
        
        // Parse command-line arguments
        po::store(po::parse_command_line(argc, argv, desc), vm);

        // Parse environment variables
        po::store(po::parse_environment(desc, [](const std::string& env_var) {
            // Convert environment variable names to option names
            if (env_var == "DATABASE_URL") return std::string("database-url");
            if (env_var == "MAX_WORKERS") return std::string("max-workers");
            if (env_var == "DEBUG_MODE") return std::string("debug-mode");
            if (env_var == "API_KEY") return std::string("api-key");
            return std::string("");
        }), vm);

        po::notify(vm);

        if (vm.count("help")) {
            std::cout << desc << std::endl;
            std::cout << std::endl << "Environment variables:" << std::endl;
            std::cout << "  DATABASE_URL    Database connection URL" << std::endl;
            std::cout << "  MAX_WORKERS     Maximum number of workers" << std::endl;
            std::cout << "  DEBUG_MODE      Debug mode (true/false)" << std::endl;
            std::cout << "  API_KEY         API key" << std::endl;
            return 0;
        }

        // Check required parameters
        if (!vm.count("database-url")) {
            std::cerr << "Error: Database URL required (--database-url or DATABASE_URL env var)" << std::endl;
            return 1;
        }

        if (!vm.count("api-key")) {
            std::cerr << "Error: API key required (--api-key or API_KEY env var)" << std::endl;
            return 1;
        }

        std::cout << "Application configuration:" << std::endl;
        std::cout << "  Database URL: " << vm["database-url"].as<std::string>() << std::endl;
        
        if (vm.count("admin-email")) {
            std::cout << "  Admin email: " << vm["admin-email"].as<email_address>().addr << std::endl;
        }
        
        std::cout << "  Max workers: " << vm["max-workers"].as<int>() << std::endl;
        std::cout << "  Debug mode: " << (vm["debug-mode"].as<bool>() ? "enabled" : "disabled") << std::endl;
        std::cout << "  API key: " << vm["api-key"].as<std::string>().substr(0, 8) << "..." << std::endl;

    } catch(po::validation_error& e) {
        std::cerr << "Validation error: Invalid value entered" << std::endl;
        return 1;
    } catch(std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

Sample Configuration File (config.ini)

# Server configuration
host = 0.0.0.0
port = 3000
timeout = 60
max-connections = 200

# Log configuration
log-level = debug

# Security configuration
ssl-enabled = true

CMake Usage

# CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(MyBoostApp)

# Find Boost libraries
find_package(Boost REQUIRED COMPONENTS program_options)

add_executable(my_app main.cpp)
target_link_libraries(my_app Boost::program_options)
target_compile_features(my_app PRIVATE cxx_std_11)