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...

1
  • this is just a typo, since problem is not argument of lambda, but how lambda is passed. Commented Sep 27, 2020 at 18:52

2 Answers 2

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

Sign up to request clarification or add additional context in comments.

4 Comments

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?
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 stackoverflow.com/questions/8711391
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:ideone.com/n9bySW
Edit: Added an image of the compiler error in the initial question ^^.
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;
});

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.