限制仿函数参数类型和常量
Restrict functor parameter type and constness
我正在尝试实施资源保护 class,它将数据与共享互斥体(实际上是 QReadWriteLock,但它很相似)结合在一起。 class 必须提供在获取锁时将用户定义的函数应用于数据的方法。我希望此 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好像没有限制参数constness,所以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
和 return 类型 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 提供了一个解决方法,但我将解释为什么您无法通过这种常规方式实现该方法。
问题是:
- 当从非 const 对象调用时,重载解析优先选择非 const 成员函数而不是 const 成员函数
- 这个签名
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
引用。因此,non-const
apply 的 membr 函数将始终被 Overload 解决方案所首选。
现在,以第一个调用为例 //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 的方法似乎有效,使用了辅助专业化 class:
#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 &
现在可以修改专用 class 中的 helper()
static class 以采用 this
参数,然后使用它蹦床回到原始模板的 class 方法。
只要您的 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; });
}
我正在尝试实施资源保护 class,它将数据与共享互斥体(实际上是 QReadWriteLock,但它很相似)结合在一起。 class 必须提供在获取锁时将用户定义的函数应用于数据的方法。我希望此 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好像没有限制参数constness,所以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
和 return 类型 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 提供了一个解决方法,但我将解释为什么您无法通过这种常规方式实现该方法。 问题是:
- 当从非 const 对象调用时,重载解析优先选择非 const 成员函数而不是 const 成员函数
- 这个签名
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
引用。因此,non-const
apply 的 membr 函数将始终被 Overload 解决方案所首选。
现在,以第一个调用为例 //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 的方法似乎有效,使用了辅助专业化 class:
#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 &
现在可以修改专用 class 中的 helper()
static class 以采用 this
参数,然后使用它蹦床回到原始模板的 class 方法。
只要您的 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; });
}