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

5

18
//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?

Nopar answered 20/3, 2013 at 21:34 Comment(2)
Please post the command line arguments you're passing to the program.Ecclesiastic
nit: it is superfluous to convert the result from std::string::c_str() to a std::stringGoner
G
18

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).

Goner answered 20/3, 2013 at 21:59 Comment(0)
A
4

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")
    ;
Aerobatics answered 20/3, 2013 at 21:48 Comment(2)
partially correct, a boost::bad_any_cast exception will still be thrown when casting an option that is not present in the po::variables_map to a std::string.Goner
Yes but that is because of the unfortunate usage of the operator[] which inserts an element if given key does not exists. The inserted element is an empty boost::any which obviously can't be any_caste-ed. Using at or find would be more appropriate.Hortative
C
2

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!

Carlitacarlo answered 11/4, 2016 at 0:27 Comment(0)
T
1

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"].

Tiannatiara answered 21/1, 2021 at 22:39 Comment(0)
C
0

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.

Cubitiere answered 11/3, 2018 at 15:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.