Search code examples
c++lambdaclosuresstd-function

How to prevent compilation of passed lambda, if arguments are not references


In one of my projects I'm using a small utility function, which takes a Message struct and a lambda function, that modifies this message struct.

Now, I unintentionally passed a lambda without the necessary reference &. It perfectly compiles, but doesn't gave the desired output.

As for me, there should be one of the two following behaviors:

  1. Forgetting to write auto&, but just auto should lead to compilation errors
  2. Writing just auto should be interpreted as auto&.

It is possible to prevent compilation in case of a missing & or even better to interpret auto as auto& automatically?

#include <iostream>
#include <functional>
#include <boost/variant.hpp>

struct Message {
    int x;
    int y;
};

void changeMessage(Message& m, const std::function<void(Message&)>& messageModifier) {
    std::cout << "Message before:" << m.x << " " << m.y << "\n";
    messageModifier(m);
    std::cout << "Message after:" << m.x << " " << m.y << "\n";
}

int main(int, char**) {
    {
        std::function<void(int&)> f = [](int&) {};
        std::function<void(int)> g = [](int) {};
        f = g; // This compiles. 
    }

    {
        std::function<void(int&)> f = [](int&) {};
        std::function<void(int)> g = [](int) {};
        //g = f; // This does not compile. Makes perfect sense.
    }

    Message m{ 10,20 };
    {
        changeMessage(m, [](auto m) { m.x++; m.y--; }); // User unintentionally forgot &! Can I prevent this from compilation?
        std::cout << "Message outside: " << m.x << " " << m.y << "\n";
    }
    {
        changeMessage(m, [](auto& m) { m.x++; m.y--; });
        std::cout << "Message outside: " << m.x << " " << m.y << "\n";
    }
}

Solution

  • One way to prevent passing Message by value (and auto itself is never a reference) is to disable copy construction:

    struct Message {
        Message() = default;
        Message(const Message&) = delete;
    
        int x;
        int y;
    };
    

    Another solution suggested by @L. F. is to check that lambda doesn't accept rvalues:

    template<class Fn>
    void change_message(Message& m, Fn fn) {
        static_assert(!std::is_invocable_v<Fn, Message&&>);     
        fn(m);
    }