Я пытаюсь реализовать класс защиты ресурсов, который бы объединял данные вместе с общим мьютексом (на самом деле, QReadWriteLock, но он похож). Класс должен предоставить метод для применения определяемой пользователем функции к данным при получении блокировки. Я хотел бы, чтобы этот метод применения работал по-разному в зависимости от параметра функции (ссылка, константная ссылка или значение). Например, когда пользователь передает такую функцию, как 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 &)
, а это не то, что мне нужно. Также в этом случае он не может вывести тип результата лямбды, поэтому мне нужно указать его вручную:
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)
В основном происходит то же самое: пока объект не является константным, вызывается изменяемая версия приложения, а result_of ничего не ограничивает.
Есть ли способ добиться этого?