简体   繁体   中英

Variadic Function template not playing nice with std::function as argument

I'm trying to create a thread-safe std::map wrapper. To avoid data-loss scenarios at the risk of misuse causing a resynchronization of the threads, I'm trying to implement a function into that wrapper that can operate directly on the internal std::map instance without breaking the std::lock_guard 's scope. I had this working as expected a couple hours ago, but decided to change the function's definition to use std::function from <functional> instead, because some of those operations are so short they would be better run from a lambda.

I was hoping you guys can tell me what I'm doing wrong. I believe it has to do with the variadic templating of the function, because eliminating that and defining the function without it produces a functioning example.

Old, Working Format:

template <class T, class U, class V = std::less<T>> class Map {
    std::map<T,U,V> MAP;
    mutable std::mutex LOCK;
public:
    template <class... Args>
    void performOperation(void(*funct)(std::map<T,U,V>&, Args&...), Args&... args){
        std::lock_guard<std::mutex> lk (LOCK);
        funct(MAP, args...);
    }
};

Map<int, std::string> TSMap;

void functionThatDoesStuff(std::map<int, std::string>& tsm, const int& k, const std::string& v){
    //doStuff
}

int memberFunctionOfAnotherClass(const int& key, const std::string& val){
    TSMap.performOperation(functionThatDoesStuff, key, val);
}

Working, non-variadic:

template <class T, class U, class V = std::less<T>> class Map {
    std::map<T,U,V> MAP;
    mutable std::mutex LOCK;
public:
    void performOperation(std::function<void (std::map<T,U,V>&)> funct){
        std::lock_guard<std::mutex> lk (LOCK);
        funct(MAP);
    }
};

Map<int, std::string> TSMap;

int memberFunctionOfAnotherClass(const int& key, const std::string& val){
    TSMap.performOperation([](std::map<int, std::string>& tsm){
        //doStuff
    });
}

New, broken format:

template <class T, class U, class V = std::less<T>> class Map {
    std::map<T,U,V> MAP;
    mutable std::mutex LOCK;
public:
    template <class... Args>
    void performOperation(std::function<void (std::map<T,U,V>&, Args...)> funct, Args&... args){
        std::lock_guard<std::mutex> lk (LOCK);
        funct(MAP, args...);
    }
};

Map<int, std::string> TSMap;

int memberFunctionOfAnotherClass(const int& key, const std::string& val){
//  I have tried every different combination of const and ampersand-based referencing here to no avail
//                                                             v      v
    TSMap.performOperation([](std::map<int, std::string>& tsm, int k, std::string v){
        //doStuff
    }, key, val);
}

The error produced by the third code block is:

no instance of function template "Map<T,U,V>::performOperation [with T=int, U=std::string, V=std::less<int>]" matches the argument list
argument types are: (lambda []void (std::map<int, std::string, std::less<int>, std::allocator<std::pair<const int, std::string>>> &tsm, int k, std::string v)->void, const int, const std::string)
object type is: Map<int, std::string, std::less<int>>

After having spent the last few hours fiddling with the issue, I couldn't figure out how Igor R's suggestion to suppress the type deduction should be implemented, and wasn't content with the idea of suppressing errors, I started testing other options.

It turns out I had a perfectly good answer when I posted this question. I still can't figure out why the templating breaks the function, but it has built and worked as expected to combine the first and second examples for something like the following:

template <class T, class U, class V = std::less<T>> class Map {
    std::map<T,U,V> MAP;
    mutable std::mutex LOCK;
public:
    template <class... Args>
    void performOperation(void(*funct)(std::map<T,U,V>&, Args&...), Args&... args){
        std::lock_guard<std::mutex> lk (LOCK);
        funct(MAP, args...);
    }

    bool performOperation(std::function<bool (std::map<T,U,V>&)> funct){
        std::lock_guard<std::mutex> lk (LOCK);
        return funct(MAP);
    }
};

Map<int, std::string> TSMap;

void functionThatDoesStuff(std::map<int, std::string>& tsm, const int& k, const std::string& v){
    //doStuff
}

int memberFunctionOfAnotherClass(const int& key, const std::string& val){
    TSMap.performOperation(functionThatDoesStuff, key, val);
    TSMap.performOperation([&](std::map<int, std::string>& tsm)->bool{
        //doStuff, key and val are available
        return true;
    });
}

It wasn't necessary to change to a boolean return on the lambda, I just wanted it in my implementation. This setup works just fine for me.

Let's reduce your example to the following:

#include <functional>

template <class T>
void bad_foo(std::function<void(T)>, T)
{}

int main() {
   bad_foo([](int){}, 1);
}

When the compiler specializes bad_foo function template, it attempts to deduce the types of the template parameters, based on the arguments you pass to the function.

The both parameters are in a "deduced context", so the compiler tries to deduce the both. Although it is able to deduce T from the second argument, the deduction fails for the first one - because lambda is not std::function . Note that the compiler does not perform any conversions at this stage!

The most simple way to work around this issue is to put the first parameter in a non-deducible context .

Like this :

#include <functional>

template <class T>
struct undeduce {
    using type = T;
};

template <class T>
using undeduce_t = typename undeduce<T>::type;

template <class T>
void good_foo(undeduce_t<std::function<void(T)>>, T)
{}

int main() {
   good_foo([](int){}, 1);
}

As a side note, there is a very common real-life example, which demonstrates nearly the same problem. Think why wouldn't the following code compile, and how to fix it:-).

 #include <numeric>
 #include <vector>

 int main() {
     std::max(std::vector<int>{}.size(), 1); 
 }

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