Search code examples
c++boostboost-program-options

boost::program_options for single-byte variables


*emphasized text*How can I use Boost program options to accept single-byte variables from the command line?

Command line parameters of --id1=1 --id2=1 results in values of id1=49 (or '1', 0x31) and id2=1.

#include <stdint.h>
#include <iostream>
#include <boost/program_options.hpp>

using namespace std;

int main(int argc, char** argv)
{
    (void)argc;
    (void)argv;
    namespace po = boost::program_options;

    const int myargc = 3;
    const char* myargv[] = {"foo","--id1=1","--id2=2" };

    uint8_t  id1;
    uint16_t id2; // works as expected.

    po::variables_map vm;
    po::options_description cmd_options( "Command options" );
    cmd_options.add_options()
    ( "id1", po::value<uint8_t >( &id1 )->default_value( 0 ), "A 1-byte ID" )
    ( "id2", po::value<uint16_t>( &id2 )->default_value( 0 ), "A 2-byte ID" )
    ;

    po::store( po::parse_command_line( myargc, myargv, cmd_options ), vm );
    po::notify( vm );
    // Using command line parameters of --id1=1 --id2=1,    
    // at this point, id1=49 (or '1', 0x31) and id2=1.
    cout << "BPO parsing of " << myargv[1] << " and " << myargv[2] << endl;
    cout << "id1: " <<      id1 << endl;
    cout << "id1: " << (int)id1 << endl;
    cout << "id2: " <<      id2 << endl;

    id1 = boost::lexical_cast<uint8_t>("1");
    id2 = boost::lexical_cast<int>("2");

    cout << "Using boost::lexical_cast" << endl;
    cout << "id1: " <<      id1 << endl;
    cout << "id1: " << (int)id1 << endl;
    cout << "id2: " <<      id2 << endl;

}

output is:

BPO parsing of --id1=1 and --id2=2
id1: 1
id1: 49
id2: 2
Using boost::lexical_cast
id1: 1
id1: 49
id2: 2

Boost eventually calls boost::lexical_cast("1")' which converts as a char rather than a numeric value - a "1" becomes a '1' which is 49.

Is there a way to change the boost::program_options::add_options() initialization to treat single-bye values as ints rather than string/char? If not, what options do I have to change the parsing or mapping? Obvious (but unfavorable) options are: [1] don't use char-like values [2] manually parse (bypass Boost) or [3] perform a secondary conversion after Boost does its parsing.


Solution

  • Create a numeric byte class that is the size of a byte but streams like a numeric value rather than streaming like a char.

    #include <stdint.h>
    #include <iostream>
    #include <boost/program_options.hpp>
    
    using namespace std;
    
    struct NumByte
    {
        uint8_t value;
    
        NumByte() : value() {}
        NumByte( const uint8_t &arg ) : value(arg) {}
        NumByte( const NumByte &arg ) : value(arg.value) {}
    
        operator uint8_t() const { return value; }
    
        friend istream& operator>>(istream& in, NumByte& valArg)
        {
            int i;
            in >> i;
            valArg.value = static_cast<uint8_t>(i);
            return in;
        }
    
        friend ostream& operator<<(ostream& out, NumByte& valArg)
        {
            out << static_cast<int>(valArg.value);
            return out;
        }
    };
    
    int main(int argc, char** argv)
    {
        (void)argc;
        (void)argv;
        namespace po = boost::program_options;
    
        const int myargc = 3;
        const char* myargv[] = {"foo","--id1=1","--id2=2" };
    
        NumByte  id1;
        uint16_t id2; 
    
        po::variables_map vm;
        po::options_description cmd_options( "Command options" );
        cmd_options.add_options()
          ( "id1", po::value<NumByte >( &id1 )->default_value( 0 ), "A 1-byte ID" )
          ( "id2", po::value<uint16_t>( &id2 )->default_value( 0 ), "A 2-byte ID" )
          ;
    
        po::store( po::parse_command_line( myargc, myargv, cmd_options ), vm );
        po::notify( vm );
    
        assert( sizeof(NumByte)==1 ); // insure the size of a numeric byte is the size of a byte.
    
        cout << "BPO parsing of " << myargv[1] << " and " << myargv[2] << endl;
        cout << "id1: " <<      id1 << endl;
        cout << "id1: " << (int)id1 << endl;
        cout << "id2: " <<      id2 << endl;
    
        id1 = boost::lexical_cast<NumByte>("1");
        id2 = boost::lexical_cast<int>("2");
    
        cout << "Using boost::lexical_cast" << endl;
        cout << "id1: " <<      id1 << endl;
        cout << "id1: " << (int)id1 << endl;
        cout << "id2: " <<      id2 << endl;
    
    }
    

    output is:

    BPO parsing of --id1=1 and --id2=2
    id1: 1
    id1: 1
    id2: 2
    Using boost::lexical_cast
    id1: 1
    id1: 1
    id2: 2