4

I'm defining a class like this:

class foo {
public:
  // I define const and non-const versions of the 'visit' function
  // nb. the lambda is passed by reference
  virtual void visitWith(std::function<void(foo&)>&);
  virtual void visitWith(std::function<void(const foo&)>&) const;
};

foo can have children so the idea is to visit a foo and all it's children recursively.

When I try to use it, eg. like this:

foo f;
f.visitWith([&](const foo&) {
  // Do something here
});

I get compiler errors. The compiler can't figure out what to do.

I can make it work it by adding a typecast like this:

foo f;
f.visitWith( (std::function<void(const foo&)>) [&](const foo&) {
  // Do something here
});

But that's horrible.

How can I get it to work neatly?

Edit:

This may be a problem with Visual C++, it refuses to compile the code given here:

https://ideone.com/n9bySW

The VC++ output when I try to compile it is:

I get this error in Visual C++

Edit2: Nope, Visual C++ is correct, the code is ambiguous. See my solution below...

Chifti Saidi
  • 438
  • 5
  • 9
  • this is [just a typo](https://godbolt.org/z/n7xd38), since problem is not argument of lambda, but how lambda is passed. – Marek R Sep 27 '20 at 18:52

2 Answers2

8

A lambda is a compiler-generated type, it is not an instance of std::function, but it is assignable to one.

Your visitWith() method takes a std::function by non-const reference, which means it requires a pre-existing std::function object, eg:

std::function<void(const foo&)> func = [&](const foo&) {
    // Do something here
};
foo f;
f.visitWith(func);

Passing a lambda directly to visitWith() would require the compiler to create a temporary std::function object, but a non-const reference cannot bind to a temporary object. That is why your original code fails to compile.

For what you are attempting, you will have to pass the std::function either by value or by const-reference instead:

class foo {
public:
    void visitWith(std::function<void(foo&)>);
    void visitWith(std::function<void(const foo&)>) const;
};

Live Demo

class foo {
public:
    void visitWith(const std::function<void(foo&)> &);
    void visitWith(const std::function<void(const foo&)> &) const;
};

Live Demo

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Am I right in thinking that a lambda which is passed by value will do a copy of that object on every function call, which might be expensive? (depending on what was captured by the lambda), or is a lambda more like cheap-to-copy function pointer? – Chifti Saidi Sep 27 '20 at 18:25
  • 1
    A lambda passed by value does make a copy, yes. Think of a lambda as an anonymous struct with an `operator()` implemented. But your `visitWith` is not taking/passing around a lambda itself (`visitWith` would have to take a template parameter to do that), it is taking/passing around a `std::function` that refers to a lambda. See https://stackoverflow.com/questions/8711391/ – Remy Lebeau Sep 27 '20 at 18:39
  • This might be a problem with Visual C++, it refuses to compile the code shown below even though it compiles and runs on that web site:https://ideone.com/n9bySW – Chifti Saidi Sep 27 '20 at 23:51
  • Edit: Added an image of the compiler error in the initial question ^^. – Chifti Saidi Sep 28 '20 at 00:00
0

I reported this "bug" to Microsoft and got a reply, here:

https://developercommunity.visualstudio.com/content/problem/1201858/c-stdfunction-overloading-fails.html

Short version: Visual C++ is handling it correctly, ideone is wrong.

In the end I solved it by adding a third overload to foo which can add const-ness to an object, like this:

class foo {
public:
  // Use typedefs so that all the code that comes after these two functions is neater
  typedef std::function<void(Branch&)>visitor;
  typedef std::function<void(const Branch&)>const_visitor;
  
  virtual void visitWith(const visitor&);
  virtual void visitWith(const const_visitor&) const;

  // This is to thunk the third case that can happen when you start
  // a const visit from a non-const foo.
  void visitWith(const const_visitor& v) {
    static_cast<const foo*>(this)->visitWith(v);  // Add const-ness
  }

};

Now the code works, eg.:

foo f;
f.visitWith([](const foo& f) {
    std::cout << "visited a const foo!" << std::endl;
});

Chifti Saidi
  • 438
  • 5
  • 9
  • This is really no different than your original code, just using typedefs, which are not necessary. See the demo links I just added to my answer, which show my examples working. – Remy Lebeau Sep 27 '20 at 21:18