Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

g++ std::visit leaking into global namespace?

I just bounced into something subtle in the vicinity of std::visit and std::function that baffles me. I'm not alone, but the only other folks I could find did the "workaround and move on" dance, and that's not enough for me:

  • https://github.com/fmtlib/fmt/issues/851
  • https://github.com/jamboree/bustache/issues/11

This may be related to an open issue in the LWG, but I think something more sinister is happening here:

  • https://cplusplus.github.io/LWG/issue3052

Minimal Example:

// workaround 1: don't include <variant>
#include <variant>
#include <functional>

struct Target
{
  Target *next = nullptr;
};

struct Visitor
{
  void operator()(const Target &tt) const { }
};

// workaround 2: concretely use 'const Visitor &' instead of 'std::function<...>'
void visit(const Target &target, const std::function<void(const Target &)> &visitor)
{
  visitor(target);
  if(target.next)
    visit(*target.next,visitor); // workaround 3: explicitly invoke ::visit(...)
    //^^^ problem: compiler is trying to resolve this as std::visit(...)
}

int main(int argc, char **argv, char **envp)
{
  return 0;
}

Compile with g++ -std=c++17, tested using:

  • g++-7 (Ubuntu 7.5.0-3ubuntu1~18.04)
  • g++-8 (Ubuntu 8.4.0-1ubuntu1~18.04)

The net result is the compiler tries to use std::visit for the clearly-not-std invocation of visit(*target.next,visitor):

g++-8 -std=c++17 -o wtvariant wtvariant.cpp
In file included from sneakyvisitor.cpp:3:
/usr/include/c++/8/variant: In instantiation of ‘constexpr decltype(auto) std::visit(_Visitor&&, _Variants&& ...) [with _Visitor = Target&; _Variants = {const std::function<void(const Target&)>&}]’:
wtvariant.cpp:20:31:   required from here
/usr/include/c++/8/variant:1385:23: error: ‘const class std::function<void(const Target&)>’ has no member named ‘valueless_by_exception’
       if ((__variants.valueless_by_exception() || ...))
            ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/8/variant:1390:17: error: no matching function for call to ‘get<0>(const std::function<void(const Target&)>&)’
      std::get<0>(std::forward<_Variants>(__variants))...));
      ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

In my real use case, I thought someone had snuck a "using namespace std" into the header space of my tree and I was gonna be grumpy. However, this minimal example demonstrates otherwise.

Critical Question: given that I have not created nor used any namespaces, why is std::visit(...) getting involved here at all?

  • WRT workaround 1: At least in the variant header, visit(...) is declared properly in the std namespace
  • WRT workaround 2: If the second argument is anything other than a std::function, it compiles just fine, leading me to believe that something more subtle is going on here.
  • WRT workaround 3: I understand that two colons are a small price to pay, but that they are necessary at all feels dangerous to me given my expectations for what it means to put a free function like visit(...) into a namespace.

Any one of the three marked workarounds will suppress the compiler error, but I'm personally intolerant of language glitches that I can't wrap my head around (Though I understand the necessity, I'm still uneasy about how often I have to sprinkle 'typename' into templates to make them compile).

Also of note, if try to make use of other elements of the std namespace without qualification (e.g., try a naked 'cout'), the compiler properly grumps about not being able to figure out the 'cout' that I'm after, so it's not as though the variant header is somehow flattening the std namespace.

Lastly, this problem persists even if I put my visit() method in its own namespace: the compiler really wants to use std::visit(...) unless I explicitly invoke my_namespace::visit(...).

What am I missing?

like image 773
Christopher Baker Avatar asked Jun 15 '20 19:06

Christopher Baker


1 Answers

The argument visitor is an std::function, which is in the namespace std, so argument-dependent lookup finds visit in the namespace std as well.

If you always want the visit in the global namespace, say so with ::visit.

like image 171
Asteroids With Wings Avatar answered Nov 12 '22 15:11

Asteroids With Wings