可变参数函数模板不能很好地使用 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 中。

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);
}

作为旁注,有一个非常常见的现实生活示例,它演示了几乎相同的问题。想想为什么下面的代码不能编译,以及如何修复它:-)。

 #include <numeric>
 #include <vector>

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