【问题标题】:Variadic Function template not playing nice with std::function as argument可变参数函数模板不能很好地使用 std::function 作为参数
【发布时间】:2020-05-15 21:49:09
【问题描述】:

我正在尝试创建一个线程安全的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>>

【问题讨论】:

  • 你的 lambda 是无捕获的;它适用于函数指针。
  • 无法推导出模板参数,因为 lambda 不是 std:: 函数(此阶段不发生转换)。尝试抑制对第一个函数参数中所有内容的扣除(使用模板struct suppress{using type=T;}; 之类的东西包装 std:: 函数)
  • @chris 我的印象是,通过捕获传递 lambda 会尝试在适当的位置运行 lambda,而不是将其作为参数传递。 New Broken Format 在传递函数指针时也拒绝工作,就像我对 Old Working Format 所做的那样。
  • @IgorR。也许通过添加第二个模板类来替换 std::function 对象并在performOperation 中进行类型检查以确保传递的对象是有效的函数指针/lambda?喜欢template &lt;class F, class... Args&gt; void performOperation(F funct, Args&amp;... args){?它似乎并不安全......
  • 不知道为什么它不安全。如果您可以通过任何可调用对象 - 这是要走的路。 OTOH,如果你需要它是 std:: 函数,你必须抑制类型推导

标签: c++ templates lambda template-argument-deduction


【解决方案1】:

让我们将您的示例简化为以下内容:

#include <functional>

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

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

当编译器专门化bad_foo 函数模板时,它会尝试根据您传递给函数的参数来推断模板参数的类型。

这两个参数都在“推导上下文”中,因此编译器会尝试推导两者。虽然它能够从第二个参数推导出 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); 
 }

【讨论】:

    【解决方案2】:

    在花了最后几个小时摆弄这个问题之后,我不知道应该如何实现 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 上更改为布尔返回,我只是希望在我的实现中使用它。这个设置对我来说很好用。

    【讨论】:

      猜你喜欢
      • 2017-10-10
      • 1970-01-01
      • 2014-05-22
      • 1970-01-01
      • 2014-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多