可变参数函数模板不能很好地使用 std::function 作为参数
Variadic Function template not playing nice with std::function as argument
我正在尝试创建一个线程安全的 std::map
包装器。
为了避免因滥用导致线程重新同步而导致数据丢失的情况,我正在尝试在该包装器中实现一个函数,该函数可以直接在内部 std::map
实例上运行而不会破坏 std::lock_guard
的范围。
几个小时前我按预期工作,但决定将函数的定义更改为使用 std::function
而不是 <functional>
,因为其中一些操作很短,它们会更好 运行来自 lambda。
我希望你们能告诉我我做错了什么。我相信它与函数的可变参数模板有关,因为消除它并在没有它的情况下定义函数会产生一个功能示例。
旧,工作格式:
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);
}
工作,非可变参数:
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
});
}
新的、损坏的格式:
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);
}
第三个代码块产生的错误是:
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>>
在花了最后几个小时摆弄这个问题之后,我无法弄清楚 Igor R 的抑制类型推导的建议应该如何实施,并且不满足于抑制错误的想法,我开始测试其他选项。
事实证明,当我发布这个问题时,我得到了一个非常好的答案。我仍然无法弄清楚为什么模板会破坏函数,但它已经按预期构建并工作,将第一个和第二个示例组合起来,如下所示:
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;
});
}
没有必要在 lambda 上更改为布尔值 return,我只是想在我的实现中使用它。这个设置对我来说很好用。
让我们将您的示例简化为以下内容:
#include <functional>
template <class T>
void bad_foo(std::function<void(T)>, T)
{}
int main() {
bad_foo([](int){}, 1);
}
当编译器专门化 bad_foo
函数模板时,它会尝试根据您传递给函数的参数推断模板参数的类型。
这两个参数都在 "deduced context" 中,因此编译器会尝试推导这两个参数。虽然它能够从第二个参数推导出 T
,但第一个参数推导失败 - 因为 lambda 不是 std::function
。请注意,编译器在此阶段不执行任何转换!
解决此问题的最简单方法是将第一个参数放在 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);
}
作为旁注,有一个非常常见的现实生活示例,它演示了几乎相同的问题。想想为什么下面的代码不能编译,以及如何修复它:-)。
#include <numeric>
#include <vector>
int main() {
std::max(std::vector<int>{}.size(), 1);
}
我正在尝试创建一个线程安全的 std::map
包装器。
为了避免因滥用导致线程重新同步而导致数据丢失的情况,我正在尝试在该包装器中实现一个函数,该函数可以直接在内部 std::map
实例上运行而不会破坏 std::lock_guard
的范围。
几个小时前我按预期工作,但决定将函数的定义更改为使用 std::function
而不是 <functional>
,因为其中一些操作很短,它们会更好 运行来自 lambda。
我希望你们能告诉我我做错了什么。我相信它与函数的可变参数模板有关,因为消除它并在没有它的情况下定义函数会产生一个功能示例。
旧,工作格式:
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);
}
工作,非可变参数:
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
});
}
新的、损坏的格式:
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);
}
第三个代码块产生的错误是:
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>>
在花了最后几个小时摆弄这个问题之后,我无法弄清楚 Igor R 的抑制类型推导的建议应该如何实施,并且不满足于抑制错误的想法,我开始测试其他选项。
事实证明,当我发布这个问题时,我得到了一个非常好的答案。我仍然无法弄清楚为什么模板会破坏函数,但它已经按预期构建并工作,将第一个和第二个示例组合起来,如下所示:
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;
});
}
没有必要在 lambda 上更改为布尔值 return,我只是想在我的实现中使用它。这个设置对我来说很好用。
让我们将您的示例简化为以下内容:
#include <functional>
template <class T>
void bad_foo(std::function<void(T)>, T)
{}
int main() {
bad_foo([](int){}, 1);
}
当编译器专门化 bad_foo
函数模板时,它会尝试根据您传递给函数的参数推断模板参数的类型。
这两个参数都在 "deduced context" 中,因此编译器会尝试推导这两个参数。虽然它能够从第二个参数推导出 T
,但第一个参数推导失败 - 因为 lambda 不是 std::function
。请注意,编译器在此阶段不执行任何转换!
解决此问题的最简单方法是将第一个参数放在 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);
}
作为旁注,有一个非常常见的现实生活示例,它演示了几乎相同的问题。想想为什么下面的代码不能编译,以及如何修复它:-)。
#include <numeric>
#include <vector>
int main() {
std::max(std::vector<int>{}.size(), 1);
}