How to solve "boost::bad_any_cast: failed conversion using boost::any_cast" when using boost program options?

StackOverflow https://stackoverflow.com/questions/15535180

سؤال

//Using boost program options to read command line and config file data
    #include <boost/program_options.hpp>
    using namespace std;
    using namespace boost;
    namespace po = boost::program_options;

int main (int argc, char *argv[])
{
    po::options_description config("Configuration");
    config.add_options()
                ("IPAddress,i","IP Address")
                ("Port,p","Port")
                 ;

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

    cout << "Values\n";

    string address = (vm["IPAddress"].as<std::string >()).c_str();
    string port = (vm["Port"].as<std::string>()).c_str();

    cout << (vm["IPAddress"].as< string >()).c_str();
    cout << " " << (vm["Port"].as<string>()).c_str();

    return 0;

}

Are the inputted values somehow unprintable?

Here is gdb output, seems to be be cast problem:

terminate called after throwing an instance of 'boost::exception_detail::clone_impl

' what(): boost::bad_any_cast: failed conversion using boost::any_cast

        Program received signal SIGABRT, Aborted.
        0x0000003afd835935 in raise () from /lib64/libc.so.6
string address = (vm["IPAddress"].as<std::string >()).c_str();

is where the error occurs; I have tried std::string and string with the same results.

testboostpo -i 192.168.1.10 -p 5000

is the command line.

I tried declaring the types, like so:

config.add_options()
        ("IPAddress,i", po::value<std::string>(), "IP Address")
            ("Port,p", po::value<std::string>(), "Port");

but the error still occurred.

Could this be a genuine bug?

هل كانت مفيدة؟

المحلول

You see the boost::bad_any_cast exception thrown from the po::variables_map because the two const char* argument overload of po::options_description_easy_init::operator() does not specify a po::value_semantic type, so converting it to a std::string will not work. If you want to convert the value to a std::string, and it is required for your application, use the required() value semantic.

#include <boost/program_options.hpp>
namespace po = boost::program_options;

int main (int argc, char *argv[])
{
    po::options_description config("Configuration");
    config.add_options()
                ("IPAddress,i", po::value<std::string>()->required(), "IP Address")
                ("Port,p", po::value<std::string>()->required(), "Port")
                ;

    try {
        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, config),vm);
        po::notify(vm);
        std::cout << "Values" << std::endl;

        const std::string address = vm["IPAddress"].as<std::string>();
        const std::string port = vm["Port"].as<std::string>();

        std::cout << "address: " << address << std::endl;
        std::cout << "port: " << port << std::endl;
    } catch ( const std::exception& e ) {
        std::cerr << e.what() << std::endl;
        return 1;
    }

    return 0;
}

Note the added catch block since parsing can (and will, as you have noticed) throw exceptions. Here is a sample session:

samm$ ./a.out
the option '--IPAddress' is required but missing
samm$ ./a.out --IPAddress 127.0.0.1
the option '--Port' is required but missing
samm$ ./a.out --IPAddress 127.0.0.1 --Port 5000
Values
address: 127.0.0.1
port: 5000
samm$ 

Here is an online demo showing the same behavior, courtesy of COmpile LInk RUn (coliru).

نصائح أخرى

You need to declare the ip-address and port as strings when you add the options:

config.add_options()
    ("IPAddress,i", po::value<std::string>(), "IP Address")
    ("Port,p", po::value<std::string>(), "Port")
    ;

This same message can also occur if you are not handling optional arguments correctly.

Sam's solution nails required arguments and the OP's code suggests required - just mark them required. For optional inputs, the Boost PO tutorial gives us a template for checking if the option exists before converting it:

if(vm.count("address")) 
{
    const std::string address = vm["IPAddress"].as<std::string>();
    std::cout << "address: " << address << std::endl;
}
if(vm.count("port")) 
    const std::string port = vm["Port"].as<std::string>();
    std::cout << "port: " << port << std::endl;
}

My problem - I had copied/pasted and forgotten to align the if test with the usage!

I had a similar error message, but it was because I was using the shorthand i, instead of IPAddress.

// this throws the cast exception
const std::string address = vm["i"].as<std::string>();
// this does not
const std::string address = vm["IPAddress"].as<std::string>();

Boost takes the first one declared. So if your option is declared as IPAddress,i you need to use vm["IPAddres"], while i,IPAddress you need to use vm["i"].

Not necessarily the same problem as this guy had but here's something that caught me:

If you put your type in an anonymous namespace, there will be two classes with the same name but different instances and the casting will fail. For example:

a.hpp:

namespace {
class MyClass {...};
}

b.cpp:

#include "a.hpp"
cli_options.add_options()("test", po::value<MyClass>(), "test desc");

c.cpp:

#include "a.hpp" // THIS WILL MAKE A DIFFERENT "MyClass"
vm["test"].as<MyClass>();  // Fails at runtime.

It fails because the MyClass in b.cpp and the one in c.cpp aren't the same class. Because of the anonymous namespace.

Removing the anonymous namespace solves the problem.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top