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