p-ranav/structopt

compile error when structopt is used in namespace

chybz opened this issue · 2 comments

chybz commented

Hello and first of all, thanks for this nice piece of software !

When STRUCTOPT(...) is used inside a namespace, I encounter the following error:

/home/chybz/dev/zap/build/root/include/structopt/third_party/visit_struct/visit_struct.hpp:839:22: error: ‘visitable’ is not a class template
  839 |   template <> struct visitable<STRUCT_NAME, void> {                                      \
      |                      ^~~~~~~~~
/home/chybz/dev/zap/build/root/include/structopt/app.hpp:13:19: note: in expansion of macro ‘VISITABLE_STRUCT’
   13 | #define STRUCTOPT VISITABLE_STRUCT
      |                   ^~~~~~~~~~~~~~~~
so.cpp:16:1: note: in expansion of macro ‘STRUCTOPT’
   16 | STRUCTOPT(Options, config_file, bind_address, verbose, user, files);
      | ^~~~~~~~~
/home/chybz/dev/zap/build/root/include/structopt/third_party/visit_struct/visit_struct.hpp:839:51: error: explicit specialization of non-template ‘foo::visit_struct::traits::visitable’
  839 |   template <> struct visitable<STRUCT_NAME, void> {

Here's the minimal example I used, compiled with gcc (Debian 10.2.1-6) 10.2.1 20210110:

g++ -Wall -std=c++20 -o test test.cpp
#include <iostream>
#include <optional>
#include <vector>

#include <structopt/app.hpp>

namespace foo {

struct Options {
   std::string config_file;
   std::optional<std::string> bind_address;
   std::optional<bool> verbose = false;
   std::optional<std::pair<std::string, std::string>> user;
   std::vector<std::string> files;
};
STRUCTOPT(Options, config_file, bind_address, verbose, user, files);

}

int main(int argc, char *argv[]) {

  try {
    auto options = structopt::app("my_app").parse<foo::Options>(argc, argv);

    std::cout << "config_file  = " << options.config_file << "\n";
  } catch (structopt::exception& e) {
    std::cout << e.what() << "\n";
    std::cout << e.help();
  }
}

If I remove namespace foo { ... }, it compiles without error.

Could you please have a look ?
Thank you !

Move the STRUCTOPT macro usage outside the namespace and it'll work.

structopt uses visit_struct which has a limitation:

Note: The macro VISITABLE_STRUCT must be used at filescope, an error will occur if it is used within a namespace. You can simply include the namespaces as part of the type, e.g.

#include <iostream>
#include <optional>
#include <vector>

#include <structopt/app.hpp>

namespace foo {

struct Options {
   std::string config_file;
   std::optional<std::string> bind_address;
   std::optional<bool> verbose = false;
   std::optional<std::pair<std::string, std::string>> user;
   std::vector<std::string> files;
};

}

STRUCTOPT(foo::Options, config_file, bind_address, verbose, user, files);

int main(int argc, char *argv[]) {

  try {
    auto options = structopt::app("my_app").parse<foo::Options>(argc, argv);

    std::cout << "config_file  = " << options.config_file << "\n";
  } catch (structopt::exception& e) {
    std::cout << e.what() << "\n";
    std::cout << e.help();
  }
}

The above compiles fine.

$ g++ -I. -std=c++17 -o main main.cpp
$ ./main -h

USAGE: my_app [FLAGS] [OPTIONS] config_file files 

FLAGS:
    -v, --verbose

OPTIONS:
    -b, --bind-address <bind_address>
    -u, --user <user>
    -h, --help <help>
    -v, --version <version>

ARGS:
    config_file
    files
chybz commented

Ok. Thanks for you help !