限制仿函数参数类型和常量

时间:2016-09-19 11:17:49

标签: c++ templates c++11 functor

我正在尝试实现一个资源保护类,它将数据与共享互斥锁结合起来(实际上,QReadWriteLock,但它类似)。该类必须提供在获取锁定时将用户定义的函数应用于数据的方法。我希望这个apply方法根据函数参数(引用,const引用或值)以不同的方式工作。例如,当用户传递像int (const DataType &)这样的函数时,它不应该仅仅因为我们正在读取数据而被阻塞,相反,当函数具有像void (DataType &)这样意味着数据修改的签名时因此需要独占锁。

我的第一次尝试是使用std :: function:

template <typename T>
class Resource1
{
public:
    template <typename Result>
    Result apply(std::function<Result(T &)> &&f)
    {
        QWriteLocker locker(&this->lock);   // acquire exclusive lock
        return std::forward<std::function<Result(T &)>>(f)(this->data);
    }

    template <typename Result>
    Result apply(std::function<Result(const T &)> &&f) const
    {
        QReadLocker locker(&this->lock);    // acquire shared lock
        return std::forward<std::function<Result (const T &)>>(f)(this->data);
    }

private:
    T data;
    mutable QReadWriteLock lock;
};

但是std :: function似乎并不限制参数常量,因此std::function<void (int &)>可以轻松接受void (const int &),这不是我想要的。同样在这种情况下,它不能推导出lambda的结果类型,所以我必须手动指定它:

Resource1<QList<int>> resource1;
resource1.apply<void>([](QList<int> &lst) { lst.append(11); });     // calls non-const version (ok)
resource1.apply<int>([](const QList<int> &lst) -> int { return lst.size(); });  // also calls non-const version (wrong)

我的第二次尝试是使用std::result_of并返回SFINAE类型:

template <typename T>
class Resource2
{
public:
    template <typename F>
    typename std::result_of<F (T &)>::type apply(F &&f)
    {
        QWriteLocker locker(&this->lock);   // lock exclusively
        return std::forward<F>(f)(this->data);
    }

    template <typename F>
    typename std::result_of<F (const T &)>::type apply(F &&f) const
    {
        QReadLocker locker(&this->lock);    // lock non-exclusively
        return std::forward<F>(f)(this->data);
    }

private:
    T data;
    mutable QReadWriteLock lock;
};

Resource2<QList<int>> resource2;
resource2.apply([](QList<int> &lst) {lst.append(12); });    // calls non-const version (ok)
resource2.apply([](const QList<int> &lst) { return lst.size(); });  // also calls non-const version (wrong)

主要是同样的事情发生:只要对象是非const,就会调用apply的可变版本,而result_of不会限制任何东西。

有没有办法实现这个目标?

4 个答案:

答案 0 :(得分:1)

您可以执行以下操作

Error:(67, 23) type mismatch;
 found   : org.apache.spark.rdd.RDD[(Long, Long)]
 required: org.apache.spark.rdd.RDD[(org.apache.spark.graphx.VertexId, Any)]
Note: (Long, Long) <: (org.apache.spark.graphx.VertexId, Any), but class RDD is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
Error occurred in an application involving default arguments.
val graph = Graph(nodes, edges, noedge)

Demo

答案 1 :(得分:0)

Jarod给出了一个解决方法,但我会解释为什么你无法实现这种常规方式。 问题是:

  1. 当从非const对象调用
  2. 时,重载分辨率优先于const成员函数的非const成员函数
  3. 此签名void foo(A&)可以接受的任何对象,void foo(const A&)也可以是同一个对象。后者甚至比前者具有更广泛的约束力。
  4. 因此,要解决这个问题,你必须至少在达到2之前击败第1点。正如Jarod所做的那样。

    从您的签名(请参阅我的评论注释):

    template <typename F>
    typename std::result_of<F (T &)>::type apply(F &&f)              //non-const member function
    {
        return std::forward<F>(f)(this->data);
    }
    
    template <typename F>
    typename std::result_of<F (const T &)>::type apply(F &&f) const //const member function
    {
        return std::forward<F>(f)(this->data);
    }
    

    当你把它称为:

    resource2.apply([](QList<int> &lst) {lst.append(12); });   //1
    resource2.apply([](const QList<int> &lst) { return lst.size(); });   //2
    

    首先,请记住resource2不是const引用。因此,apply的non-const membr函数将始终优先于Overload resolution。

    现在,以第一次调用//1的情况为例,无论lambda可以调用什么,然后第二个也可以用该对象调用

    您想要做的简化模拟是:

    struct A{
        template<typename Func>
        void foo(Func&& f); //enable if we can call f(B&);
    
        template<typename Func>
        void foo(Func&& f) const; //enable if we can call f(const B&);
    };
    
    void bar1(B&);
    void bar2(const B&);
    
    int main(){
        A a;
        a.foo(bar1);
        a.foo(bar2);
    
        //bar1 and bar2 can be both called with lvalues
        B b;
        bar1(b);
        bar2(b);
    }
    

答案 2 :(得分:0)

据我了解,您希望区分参数std::function,该参数采用const引用与非常量引用。

以下基于SFINAE的方法似乎有效,使用辅助专业化类:

#include <functional>
#include <iostream>

template<typename ...Args>
using void_t=void;

template<typename Result,
     typename T,
     typename lambda,
     typename void_t=void> class apply_helper;

template <typename T>
class Resource1
{
public:

    template <typename Result, typename lambda>
    Result apply(lambda &&l)
    {
        return apply_helper<Result, T, lambda>::helper(std::forward<lambda>(l));
    }
};

template<typename Result, typename T, typename lambda, typename void_t>
class apply_helper {

 public:

    static Result helper(lambda &&l)
    {
        std::cout << "T &" << std::endl;

        T t;
        return l(t);
    }
};


template<typename Result, typename T, typename lambda>
class apply_helper<Result, T, lambda,
           void_t<decltype( std::declval<lambda>()( std::declval<T>()))>> {
 public:

    static Result helper(lambda &&l)
    {
        std::cout << "const T &" << std::endl;
        return l( T());
    }
};


Resource1<int> test;

int main()
{
    auto lambda1=std::function<char (const int &)>([](const int &i)
                               {
                                   return (char)i;
                               });
    auto lambda2=std::function<char (int &)>([](int &i)
                               {
                                   return (char)i;
                               });

    auto lambda3=[](const int &i) { return (char)i; };
    auto lambda4=[](int &i) { return (char)i; };

    test.apply<char>(lambda1);
    test.apply<char>(lambda2);
    test.apply<char>(lambda3);
    test.apply<char>(lambda4);
}

输出:

const T &
T &
const T &
T &

Demo

现在可以修改专用类中的helper()静态类来取一个this参数,然后使用它来蹦床回到原始模板的类方法。

答案 3 :(得分:0)

只要你的lambda的捕获列表是空的,你就可以依赖这样一个lambda衰减到函数指针的事实。
区分这两种类型就足够了。

它遵循一个最小的工作示例:

#include<iostream>

template <typename T>
class Resource {
public:
    template <typename Result>
    Result apply(Result(*f)(T &)) {
        std::cout << "non-const" << std::endl;
        return f(this->data);
    }

    template <typename Result>
    Result apply(Result(*f)(const T &)) const {
        std::cout << "const" << std::endl;
        return f(this->data);
    }

private:
    T data;
};

int main() {
    Resource<int> resource;
    resource.apply<void>([](int &lst) { });
    resource.apply<int>([](const int &lst) -> int { return 42; });
}