p-ranav / argparse

Argument Parser for Modern C++
MIT License
2.61k stars 242 forks source link

Embed argparse in a class #281

Closed lu-maca closed 10 months ago

lu-maca commented 1 year ago

Hi,

is it possible to embed an argument parser as a class attribute? If so, is this the correct way to do that?

File config.h

#include "argparse.hpp"
#include <string>

class Config 
{
    std::string _program_name;
    argparse::ArgumentParser _program;

public:
    Config(const std::string& program_name, int argc, char** argv);
};

Config::Config(const std::string& program_name, int argc, char** argv) : _program_name(program_name)
{
    _program = argparse::ArgumentParser(program_name);

    _program.add_argument("--test", "-t")
        .help("Testing an argument...")
        .default_value(std::string())
        .action([](const std::string& value) { return value; });

    try
    {
        _program.parse_args(argc, argv);
    }
    catch (const std::runtime_error& err)
    {
        std::cout << "Error reading arguments: " << err.what() << std::endl ;
    }
    std::cout  << _program.get<std::string>("--test") << std::endl; 
}

File main.cpp

#include "config.h"

int main (int argc, char** argv) 
{
    Config configuration{"FOO", argc, argv};
}

This works fine if I require to parse the -t argument, but as soon as the -h is provided, something strange happens: immagine

Is there something I'm missing? Thanks, Luca

lu-maca commented 1 year ago

A side note: probably this is not the best design, because one could instantiate more instances of Config. In our case, the Config class could be thought as a singleton, but for the sake of simplicity it is not in this example. I accept better design, though.

Thanks again, Luca

lu-maca commented 1 year ago

I'm not good enough to understand if there's a problem in the library, but eventually I've found out how to solve my problem. When invoking the copy assignment operator something goes wrong, so the solution to my problem is to use pointers:

#include "argparse.hpp"
#include <string>

class Config 
{
    std::string _program_name;
    argparse::ArgumentParser* _program;

public:
    Config(const std::string& program_name, int argc, char** argv);
};

Config::Config(const std::string& program_name, int argc, char** argv) : _program_name(program_name)
{
    _program = new argparse::ArgumentParser(program_name);

    _program->add_argument("--test", "-t")
        .help("Testing an argument...")
        .default_value(std::string())
        .action([](const std::string& value) { return value; });

    try
    {
        _program->parse_args(argc, argv);
    }
    catch (const std::runtime_error& err)
    {
        std::cout << "Error reading arguments: " << err.what() << std::endl ;
    }
    std::cout  << _program->get<std::string>("--test") << std::endl; 
}

As said above, I'm not good enough to fully understand the copy assignment operator implementation in the library, so I can't tell if there's something wrong or it's just my implementation that is bad. Any explanation is welcome! :)

Luca

marzer commented 1 year ago

It'd probably work as you originally had it if you initialized _program as part of the in-class initializer list, instead of copy/move-assigning it later (which you should always do as a matter of good C++ style anyways):

Config::Config(const std::string& program_name, int argc, char** argv)
    : _program_name(program_name),
    _program(program_name)
{
    // ...
}

That being said, it does appear that you've identified a bug in the ArgumentParser move-assignment operator.

lu-maca commented 1 year ago

Thanks for the help @marzer!