1

from here, if I put the overloaded operator in the namespace it should just work. But I found it actually not working, compiler always complain that it cannot find the << or >>.

In following example, if I turn on the USE_NAMESPACE, compiling fails. But the >> and << actually work outside the program_options. If I turn off the ENABLE_PO it works fine.

What happens here? how could I fix it?

#include <iostream>
#include <sstream>
#include <string>
#include <boost/program_options.hpp>
namespace po = boost::program_options;
#define ENABLE_PO 1
#define USE_NAMESPACE 0
enum class test_enum_t {bar, foo};
#if USE_NAMESPACE
namespace test_enum
{
#endif

    template<typename istream_T>
    istream_T& operator>>(istream_T& in, test_enum_t& value)
    {
        std::string in_str;
        in >> in_str;
        if (in_str == "bar")
            value = test_enum_t::bar;
        else if(in_str == "foo")
            value = test_enum_t::foo;
        else
            in.setstate(std::ios_base::failbit);
        return in;
    }

    template<typename ostream_T>
    ostream_T &operator<<(ostream_T &out, const test_enum_t& value) {
        if (value == test_enum_t::bar)
            out << "bar";
        else if (value == test_enum_t::foo)
            out << "foo";
        else
            out << "unknown-value";
        return out;
    }

#if USE_NAMESPACE
}
using namespace test_enum;
#endif

#if ENABLE_PO
int simple_options(int argc, char *argv[])
{
    test_enum_t test = test_enum_t::bar;
    po::options_description config("Configuration");
    config.add_options()
        ("help", "produce help message")
        ("in,i", po::value(&test)->default_value(test), "test enum class as input.")
        ;
    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, config),vm);
    po::notify(vm);
    if (vm.count("help")) {
        std::cout << config << "\n";
        return 1;
    }

    return 0;
}
#endif
int main(int argc, char *argv[])
{
#if ENABLE_PO
    simple_options(argc, argv);
#endif
    test_enum_t test;
    std::cout << "input bar or foo" << std::endl;
    std::cin >> test;
    if (std::cin.fail())
    {
        std::cerr << "not a valid value";
        exit(1);
    }
    std::cout << test << std::endl;
    return 0;
}

Wang
  • 7,250
  • 4
  • 35
  • 66
  • 1
    You forgot to put `test_enum_t` in the same namespace as the operators. ADL requires the type and the function be in the same namespace. – NathanOliver May 06 '20 at 13:10
  • this is difficult, in reality the operator template will be in a separated lib like magic_enum, which defines the operator templates in a namespace. So I cannot define all the enum_t in that namespace, which would be very messy. Is there any way to work around this? – Wang May 06 '20 at 13:29
  • AFAIK for it to work the type and the operator need to be in the same namespace. – NathanOliver May 06 '20 at 13:30
  • is there any way to do `using somenamespace::operator<<` for templates? or maybe specialization in another namesapce where the enum_t defined? – Wang May 06 '20 at 13:37
  • That should work. Is it not? You still need to put that in the namespace the enum is in. – NathanOliver May 06 '20 at 13:41
  • Thanks it works. – Wang May 06 '20 at 13:48

1 Answers1

1

As mentioned, you need ADL (Argument Dependent Lookup) to succeed.

If you can control POI (Point Of Instantiation) you can also actively use the names from the dedicated namespace instead. In this case, you need to use these names in the TU that defines simple_options before the point that instantiates the parsing code, e.g. where you define the Option Descriptions Component.

Here's a simplified version of the code that demonstrates it

Live On Coliru

#include <boost/program_options.hpp>
#include <iostream>
#include <sstream>
#include <string>
enum class test_enum_t { bar, foo };

namespace test_enum {

    template <typename istream_T>
        istream_T& operator>>(istream_T& in, test_enum_t& value) {
            std::string in_str;
            in >> in_str;
            if (in_str == "bar")
                value = test_enum_t::bar;
            else if (in_str == "foo")
                value = test_enum_t::foo;
            else
                in.setstate(std::ios_base::failbit);
            return in;
        }

    template <typename ostream_T>
        ostream_T& operator<<(ostream_T& out, const test_enum_t& value) {
            if (value == test_enum_t::bar)
                out << "bar";
            else if (value == test_enum_t::foo)
                out << "foo";
            else
                out << "unknown-value";
            return out;
        }

}

using test_enum::operator<<;
using test_enum::operator>>;

test_enum_t simple_options(int argc, char* argv[]) {
    namespace po = boost::program_options;

    test_enum_t test = test_enum_t::bar;
    po::options_description config("Configuration");

    config.add_options()("help", "produce help message")(
            "in,i", po::value(&test)->default_value(test),
            "test enum class as input.");

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, config), vm);
    po::notify(vm);
    if (vm.count("help")) {
        std::cout << config << "\n";
        exit(1);
    }
    return test;
}

int main(int argc, char* argv[]) {
    std::cout << "Command line input: " << simple_options(argc, argv) << "\n";

    std::cout << "input bar or foo" << std::endl;
    if (test_enum_t test; std::cin >> test) {
        std::cout << test << std::endl;
    } else {
        std::cerr << "not a valid value";
        return 1;
    }
}

Prints

g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp -lboost_program_options
./a.out -i bar <<<foo
Command line input: bar
input bar or foo
foo
sehe
  • 374,641
  • 47
  • 450
  • 633