Home > Software engineering >  A lambda with a capture fail to compile when used with std::semi_regular
A lambda with a capture fail to compile when used with std::semi_regular

Time:06-20

The following example fails to compile as soon as the lambda capture any variable. If the capture is removed the compiler will successfully compile the code below.

#include <concepts>
#include <functional>
#include <iostream>

template<typename T>
concept OperatorLike = requires(T t, std::string s) {
    { t[s] } -> std::same_as<std::string>;
};

template<typename T, typename O>
concept Gettable = requires(T t, O op) { 
t.apply_post(0, op); };

template<std::semiregular F>
class RestApiImpl {
    F m_post_method;
    public:
    RestApiImpl(F get = F{}) : m_post_method{std::move(get)} {}

    template<OperatorLike IF>
    requires std::invocable<F, const int, IF>
    void apply_post(const int req, IF interface){ 
        m_post_method(req, std::move(interface));  
    }
};


int main(){

    int ii;
    auto get = [&ii](int,  OperatorLike auto intf){ 
            std::string dummy = "dummy";
        std::cout << intf[dummy];
    };
    RestApiImpl api(get);
    return 0;
};

If the lambda function get() has empty brackets [] , meaning no capture it will compile.

The need of the class RestApiImpl is based on following application:

class Server{
public:


   struct impl;

   void run(Gettable<impl> auto& api){
      api.apply_post(0, impl(*this));
   }

   struct impl {
        public:
        Server& m_server;
        impl(Server& server ) : m_server(server){}
        std::string operator[](std::string key) {
            return "dummy_for_now";
        }
   };
};

int main(){
    auto get = [&ii](int,  OperatorLike auto intf){ 
            std::string dummy = "dummy";
        std::cout << intf[dummy];
    };
    RestApiImpl api(get);
    
    Server server;
    server.run(api);
    
    return 0;
};

The error message:

:35:24: error: class template argument deduction failed: 35 | RestApiImpl api(get); | ^ :35:24: error: no matching function for call to 'RestApiImpl(main()::&)' :18:5: note: candidate: 'template RestApiImpl(F)-> RestApiImpl' 18 | RestApiImpl(F get = F{}) : m_post_method{std::move(get)} {} | ^~~~~~~~~~~ :18:5: note: template argument deduction/substitution failed: : In substitution of 'template RestApiImpl(F)-> RestApiImpl [with F = main()::]': :35:24: required from here :18:5: error: template constraint failure for 'template requires semiregular class RestApiImpl' :18:5: note: constraints not satisfied

What options to I have in terms of overcoming this ?`

CodePudding user response:

std::semiregular requires both std::copyable and std::default_initializable.

std::copyable means that it requires the type to be copy-constructible and copy-assignable.

std::default_initializable means that it requires the type T to have well-formed initializations of the forms T t;, T() and T{}.

A lambda with a capture has a deleted copy assignment operator, no move assignment operator and no default constructor, while for a capture-less lambda (since C 20) all of them are defaulted. As a consequence a lambda with capture is not std::copyable (or std::movable for that matter) and not std::default_initializable and so especially also not std::semiregular, while a capture-less lambda is all of these.

Your RestApiImpl requires that the passed argument for F get in the constructor be std::semiregular.


To resolve this rethink whether RestApiImpl really requires all of these properties of the type.

I can see that it requires move-constructibility in m_post_method{std::move(get)}, but it is unclear where it uses e.g. copy-assignability from what you are showing.

= F{} uses default-constructibility, but that default value is not used or instantiated if you are not default-constructing a RestApiImpl instance with that type.

Then use concepts which are not as restrictive as std::semiregular.

If that is not a possibility, then you cannot use a lambda like this directly. Instead you can use an old-style functor type, e.g. a class with overloaded operator() and have it store a std::reference_wrapper<int> to the ii object. This assures assignability, because a simple reference as member would make the class non-assignable.

CodePudding user response:

If you capture ii by reference, the lambda is neither default constructible nor copyable. The std::semiregular concept requires this tough.

The template parameter deduced for RestApiImpl does not fulfill the requirements imposed on it.

  • Related