Stéphane Janel
Stéphane Janel

Reputation: 410

Cannot compile variant visitor access on MSVC 19.28

I try to compile a personal project on Visual Studio 2019 (using MSVC 19.28 compiler) and I came accross a compilation error in the std::visit which I don't understand:


<source>(131): error C2653: '`global namespace'': is not a class or namespace name
C:/data/msvc/14.28.29914/include\type_traits(1493): note: see reference to function template instantiation 'auto CommandLineOptionsParser<CmdLineOpts>::register_callback::<lambda_1>::()::<lambda_1>::operator ()<const _First&>(_T1) const' being compiled
        with
        [
            _First=bool CmdLineOpts::* ,
            _T1=bool CmdLineOpts::* const &
        ]
C:/data/msvc/14.28.29914/include\variant(1654): note: see reference to alias template instantiation 'std::_Variant_visit_result_t<CommandLineOptionsParser<CmdLineOpts>::register_callback::<lambda_1>::()::<lambda_1>,const std::variant<bool CmdLineOpts::* >&>' being compiled
<source>(120): note: while compiling class template member function 'void CommandLineOptionsParser<CmdLineOpts>::register_callback(const CommandLineOption &,std::variant<bool CmdLineOpts::* >)'
<source>(83): note: see reference to function template instantiation 'void CommandLineOptionsParser<CmdLineOpts>::register_callback(const CommandLineOption &,std::variant<bool CmdLineOpts::* >)' being compiled
<source>(142): note: see reference to class template instantiation 'CommandLineOptionsParser<CmdLineOpts>' being compiled
<source>(123): error C2672: 'visit': no matching overloaded function found
<source>(131): error C2893: Failed to specialize function template 'unknown-type std::visit(_Callable &&,_Variants &&...)'
C:/data/msvc/14.28.29914/include\variant(1654): note: see declaration of 'std::visit'
<source>(131): note: With the following template arguments:
<source>(131): note: '_Callable=CommandLineOptionsParser<CmdLineOpts>::register_callback::<lambda_1>::()::<lambda_1>'
<source>(131): note: '_Variants={const std::variant<bool CmdLineOpts::* > &}'
<source>(131): note: '<unnamed-symbol>=void'
Compiler returned: 2

This code compiles fine with gcc.

I tested the code snippet from cppreference on std::visit and it compiles with MSVC, so I am not so sure what the issue here.

I simplified the code and reproduced the issue on godbolt

Here's the code

#include <algorithm>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <set>
#include <string_view>
#include <variant>
#include <type_traits>

using InvalidArgumentException = std::invalid_argument;
using CommandLineOption = std::string;

template <class Opts>
class CommandLineOptionsParser : Opts {
 public:
  using OptionType = std::variant<bool Opts::*>;
  using CommandLineOptionWithValue = std::pair<CommandLineOption, OptionType>;

  Opts parse(const char* argStr) {
    // First register the callbacks
    bool Opts::* pBool = &Opts::help;
    register_callback("help", pBool);

    for (auto& cbk : _callbacks) {
      cbk.second(0, argStr);
    }

    return static_cast<Opts>(*this);
  }

 private:
  using callback_t = std::function<void(int, const char *)>;

  std::map<CommandLineOption, callback_t> _callbacks;

  void register_callback(const CommandLineOption& commandLineOption, OptionType prop) {
    _callbacks[commandLineOption] = [this, &commandLineOption, prop](int idx, const char * argv) {
      if (std::string(argv) == commandLineOption) {
        std::visit([](auto&& a)  {
              using T = std::decay_t<decltype(a)>;
              if constexpr (std::is_same_v<T, bool Opts::*>) {
                std::cout << "bool" << std::endl;
              }
            },
            prop);
      }
    };
  };
};

struct CmdLineOpts {
  bool help{};
};

int main(int argc, const char* argv[])
{
    CommandLineOptionsParser<CmdLineOpts> p;
    CmdLineOpts cmdLineOptions = p.parse("opt1");    
}

Upvotes: 2

Views: 202

Answers (1)

rustyx
rustyx

Reputation: 85286

It seems MSVC is having difficulty synthesizing a lambda with a pointer-to-member argument in a template context.

I tried to simplify it to a MCVE, hopefully it captures the essence of the issue:

template<class T>
bool test(int T::* t) {
    return [](int T::* x) {
        return true;
    }(t);
}

struct A {
    int a;
};

int main() {
    return test<A>(&A::a);
}

It fails to compile in MSVC C++20 mode (but not C++17) with a similar nonsensical error (link):

<source>(5): error C2653: '`global namespace'': is not a class or namespace name
<source>(13): note: see reference to function template instantiation 'bool test<A>(int A::* )' being compiled
<source>(5): error C2664: 'bool test::<lambda_1>::operator ()(A *) const': cannot convert argument 1 from 'int A::* ' to 'A *'
<source>(5): note: There is no context in which this conversion is possible
<source>(5): note: see declaration of 'test::<lambda_1>::operator ()'

I would suggest to report this to the vendor.

As a potential workaround can try extracting the lambda into a functor class with a templated operator(), it seems to compile (example).

Upvotes: 1

Related Questions