简体   繁体   中英

Passing a Lambda Expression to std::function in C++

I'm currently struggling with lambda expressions. I created a ConcurrentDictionary which wraps std::map with a mutex. Now I'd like to export all elements that match a specified condition.

template<typename MutexTypeT, typename Key_Type_T, typename Mapped_Type_T>
class CConcurrentDictionary
{
    public:
        ///Constructor
        CConcurrentDictionary();

        class CSingleElement
        {
        public:

          ///the key
          Key_Type_T key = { };

          ///THe Mapped Type
          Mapped_Type_T mapped_type = { };

        };

     template<typename ...Args_T>
        std::vector<CSingleElement> exportSelectedData(
            const uint32_t u32MutexTimeout,
            std::function<bool(const CSingleElement, Args_T &&...)> compareFn,
            Args_T&&...CompareArgs...) const;

}

template<typename MutexTypeT, typename Key_Type_T, typename Mapped_Type_T>
   template<typename ...Args_T>
   auto CConcurrentDictionary<MutexTypeT, Key_Type_T, Mapped_Type_T>::exportSelectedData(
          const uint32_t u32MutexTimeout,
          std::function<bool(const CSingleElement, Args_T &&...)> compareFn,
          Args_T&&...CompareArgs...) const ->
      std::vector<CSingleElement>
      {
        //lock mutex...
        std::vector<CSingleElement> vecRes;
        for (const auto &single_element : dictionary)
        {
          if(compareFn(single_element, CompareArgs...))
          {
            //this element mathes the preconditions
            vecRes.push_back(single_element);
          }
        }

        return vecRes;
      }

However when I try to use this class like this

 class CTraceCmdDuration
  {

  public:


    ///Cmd Datatype
    using CmdType_T = uint32_t;

    class CCmdSummary
    {
    public:
      /**
       * SIngle Long Running Cmd Summary
       * @param CmdIdArg Cmd ID
       * @param u32MaxCmdDurationArg Max CmdDuration
       */
      CCmdSummary(const CmdType_T CmdIdArg, const uint32_t u32MaxCmdDurationArg);
      ///Id of this cmd
      const CmdType_T CmdId;

      ///Duration of this cmd
      const uint32_t u32MaxCmdDuration;
    };

/**
     * Exports all Cmds to a vector with took longer than u32MaxCmdDuration
     * @param u32MaxCmdDuration Maximal Cmd Processing duration time. Cmds that took longer than this will be exported
     * @param u32MutexTimeout Mutex Timeout
     * @return List with all long running cmds
     */
    std::vector<CCmdSummary> ExportLongRunningCmds(const uint32_t u32MaxCmdDuration, const uint32_t u32MutexTimeout) const;

}

  auto CTraceCmdDuration::ExportLongRunningCmds(const uint32_t u32MaxCmdDuration, const uint32_t u32MutexTimeout) const ->
      std::vector<CCmdSummary>
  {
    auto lambda = [u32MaxCmdDuration](const CombinedDictElement& singleElement)
        {
          const bool bRes = (u32MaxCmdDuration < singleElement.mapped_type.u32MaxProcessingDuration);
          return bRes;
        };
    auto vecRes = dict.exportSelectedData(u32MutexTimeout, lambda, u32MaxCmdDuration);
    return vecRes;
  }

This unfortunately produces no matching call to function errors

error: no matching function for call to 'NConcurrent_Container::CConcurrentDictionary<NMutex::CEmbos_Mutex, long unsigned int, NDebug::CTraceCmdDuration::CProcessingInfo>::exportSelectedData(const uint32_t&, NDebug::CTraceCmdDuration::ExportLongRunningCmds(uint32_t, uint32_t) const::__lambda0&, const uint32_t&) const'
     auto vecRes = dict.exportSelectedData(u32MutexTimeout, lambda, u32MaxCmdDuration);

What is wrong with my lambda expression? The Idea was to pass the max allowed time duration as a capture and every single stored element in the std::map as an argument.

Or do you have a better Idea? Could you help me out here?

Edit : Thx for your answer this works well if I pass a static function but how would I pass the lambda as a template argument?

static bool CompareDuaration(const CSingleElement&singleElement, const uint32_t u32MaxDuration);

  auto CTraceCmdDuration::ExportLongRunningCmds(const uint32_t u32MaxCmdDuration, const uint32_t u32MutexTimeout) const ->
      std::vector<CombinedDictElement>
  {
    auto vecRes = dict.exportSelectedData(u32MutexTimeout, lambda, u32MaxCmdDuration);
    return vecRes;
  }

This works but

auto CTraceCmdDuration::ExportLongRunningCmds(const uint32_t u32MaxCmdDuration, const uint32_t u32MutexTimeout) const ->
      std::vector<CombinedDictElement>
  {

    auto lambda = [u32MaxCmdDuration](const CombinedDictElement& singleElement)
        {
          const bool bRes = (u32MaxCmdDuration < singleElement.mapped_type.u32MaxProcessingDuration);
          return bRes;
        };

    auto vecRes = dict.exportSelectedData(u32MutexTimeout, lambda, u32MaxCmdDuration);
    return vecRes;
  }

gives me a compile error

error: no match for call to '(CTraceCmdDuration::ExportLongRunningCmds(uint32_t, uint32_t) const::__lambda0) (CConcurrentDictionary<NMutex::CEmbos_Mutex, long unsigned int, CTraceCmdDuration::CProcessingInfo>::CSingleElement&, const long unsigned int&)'
       if(compareFn(singleElement, compareArgs...))

It seams like passing lambdas to templates does not work so well. What am I Missing?

The problem

The problem you have can be reduced to this:

#include <functional>

template <typename ...Args>
void foo(std::function<bool(Args...)>) { }

int main()
{
    foo([](int, int) { return true; });
}

which will fail to compile. The reason for this is that the type deduction for the template arguments for std::function fails.

You expect a std::function of some sort to be passed as an argument. Since no std::function object (of whatever exact instantiation) is passed in, the compiler tries to construct a std::function and deduce the template parameter types by calling the constructor of std::function . Here starts the problem. The matching constructor in this case would be:

template <typename F>
function(F f);

You will notice that the constructor itself is also templated. The compiler could successfully deduce F as a lambda, but since F is a template parameter of the constructor, the template parameter of the class itself std::function cannot be deduced.

Furthermore, to quote cppreference on that constructor:

[...] This constructor does not participate in overload resolution unless f is Callable for argument types Args... and return type R. [...]

This means that the existance of this constructor is based on whether F can be called with the class template arguments Args... , but since those are not explicitly defined and cannot be deduced, this constructor won't be available anyway.

The solution

Since you only use that std::function inside exportSelectedData , simply make it a template parameter alltogether (ditching the std::function part):

template<typename Func, typename ...Args_T>
std::vector<CSingleElement> exportSelectedData(uint32_t u32MutexTimeout, Func compareFn, Args_T const&...) const;

You should also change Args_T&& to Args_T const& since you don't simply forward those arguments but reuse them inside a loop.

Edit

Regarding your follow-up question in the edit: think about what you're doing.

First you declare a lambda:

auto lambda = [u32MaxCmdDuration](const CombinedDictElement& singleElement) { /* ... */ };

Now think about the signature of that lambda. You return a boolean so the return type is bool (so far so good). You take u32MaxCmdDuration in the capture-clause and you take one argument singleElement . Lets remove all the extra qualifiers and look at the signature:

bool(CombinedDictElement) // take a CombinedDictElement and return a bool

Next, we take a look at the call of exportSelectedData :

exportSelectedData(u32MutexTimeout, lambda, u32MaxCmdDuration);

You pass in u32MutexTimeout and lambda which is perfectly fine, the lambda is captued by compareFn . The third argument is u32MaxCmdDuration which is captured by ...compareArgs in your template. Now lets take a look at where you actually invoke the lambda inside exportSelectedData :

if (compareFn(singleElement, compareArgs...)) // ...

What signature do you expect compareFn to have here? If we expand the ...compareArgs pack (again, removing the extra qualifiers for simplicity) the signature looks like this:

bool(CombinedDictElement, unsigned int) // take a CombinedDictElement and an unsigned int and return a bool

Here is the lambda signature again:

bool(CombinedDictElement)

Do you spot the problem? The lambda captures u32MaxCmdDuration as a state capture while exportSelectedData expects it as an parameter (since you passed it into exportSelectedData as an additional argument). The signatures differ from each other, obviously, so we have to change one of them to match the other. This is fairly easy in your case, either

  • change your lambda to take u32MaxCmdDuration as a parameter:

     auto lambda = [](const CombinedDictElement& singleElement, unsigned int u32MaxCmdDuration)

    or

  • remove u32MaxCmdDuration as an additional argument to your exportSelectedData call:

     exportSelectedData(u32MutexTimeout, lambda);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM