繁体   English   中英

限制函子参数类型和常量

[英]Restrict functor parameter type and constness

我正在尝试实现一个资源保护类,它将数据与共享互斥锁(实际上,QReadWriteLock,但它相似)组合在一起。 该类必须提供在获取锁时将用户定义的函数应用于数据的方法。 我希望这个 apply 方法根据函数参数(引用、常量引用或值)不同地工作。 例如,当用户传递一个像int (const DataType &)这样的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)

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

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

您可以执行以下操作

template <std::size_t N>
struct overload_priority : overload_priority<N - 1> {};

template <> struct overload_priority<0> {};

using low_priority = overload_priority<0>;
using high_priority = overload_priority<1>;

template <typename T>
class Resource
{
public:
    template <typename F>
    auto apply(F&& f) const
    // -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
    {
        return apply_impl(std::forward<F>(f), high_priority{});
    }

    template <typename F>
    auto apply(F&& f)
    // -> decltype(apply_impl(std::forward<F>(f), high_priority{}))
    {
        return apply_impl(std::forward<F>(f), high_priority{});
    }

private:
    template <typename F>
    auto apply_impl(F&& f, low_priority) -> decltype(f(std::declval<T&>()))
    {
        std::cout << "ReadLock\n";
        return std::forward<F>(f)(this->data);
    }

    template <typename F>
    auto apply_impl(F&& f, high_priority) -> decltype(f(std::declval<const T&>())) const
    {
        std::cout << "WriteLock\n";
        return std::forward<F>(f)(this->data);
    }

private:
    T data;
};

演示

Jarod 提供了一种解决方法,但我将解释为什么您无法通过这种常规方式实现该目标。 问题在于:

  1. 当从非常量对象调用时,重载解析更喜欢非常量成员函数而不是常量成员函数
  2. 无论这个签名void foo(A&)可以接受什么对象, void foo(const A&)也可以是同一个对象。 后者甚至比前者具有更广泛的绑定集。

因此,要解决它,您必须至少在到达第 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 函数将始终被重载解决方案所首选。

现在,以第一个调用//1的情况//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);
}

据我了解,您想区分一个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 &

演示

专用类中的helper()静态类现在可以修改为采用this参数,然后使用它跳回到原始模板类的方法中。

只要您的 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; });
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM