Arhn - архитектура программирования

Ограничить тип параметра функтора и константу

Я пытаюсь реализовать класс защиты ресурсов, который бы объединял данные вместе с общим мьютексом (на самом деле, 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 ничего не ограничивает.

Есть ли способ добиться этого?

19.09.2016

Ответы:


1

Вы можете сделать следующее

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;
};

Демо

19.09.2016
  • Отличная работа! Единственное, чего я не понимаю, так это зачем использовать шаблонные структуры перегрузки_приоритета, а не просто: struct low_priority {}; структура high_priority : low_priority {}; 19.09.2016
  • Мне также интересно узнать ваше мнение о таком дизайне. Общая защита данных — довольно распространенная задача, но я не нашел много информации о том, как инкапсулировать эту функциональность. Буду признателен за любые ссылки, примеры, рекомендации. 19.09.2016
  • Для приоритета это в основном шаблон, который допускает большее количество уровней, но на самом деле достаточно более простого наследования. 19.09.2016

  • 2

    Джарод предложил обходной путь, но я объясню, почему вы не можете добиться этого обычным способом. Проблема в том, что:

    1. Разрешение перегрузки предпочитает неконстантные функции-члены константным функциям-членам при вызове из неконстантного объекта.
    2. какой бы объект ни принимала эта подпись void foo(A&), void foo(const A&) также может принять тот же объект. Последний даже имеет более широкий набор привязок, чем первый.

    Следовательно, чтобы решить ее, вам нужно как минимум победить пункт 1, прежде чем перейти к пункту 2. Как это сделал Джарод.

    Из ваших подписей (см. аннотации к моим комментариям):

    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 membr команды apply всегда будет предпочтительнее при разрешении перегрузки.

    Теперь, взяв случай первого вызова //1, независимо от того, с чем эта лямбда может быть вызвана, тогда вторая также может быть вызвана с этим объектом.

    Упрощенный макет того, что вы пытаетесь сделать, это:

    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);
    }
    
    19.09.2016

    3

    Насколько я понимаю, вы хотите отличить параметр 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, а затем использовал его для обратного перехода к методу класса исходного шаблона.

    19.09.2016
  • Скомпилируйте с помощью gcc, но не с clang: Демо 19.09.2016
  • Я не знаком с диагностикой clang, но мне кажется, что у clang не было проблем с заменой любой специализации. Я не понимаю, как это возможно, чтобы иметь возможность подставлять как const std::function<Result(const T &)> *, так и const std::function<Result(T &)> *, когда T не является константным. 19.09.2016
  • Это тоже кажется странным. Но в любом случае ваш код не работает с обычной лямбдой Demo 19.09.2016
  • Верно, но нужно лишь немного больше работы, чтобы заставить его работать. Обновлено. 19.09.2016
  • [Примечание]: поскольку OP упоминает С++ 11 и может иметь устаревший компилятор, void_t может потребовать другой реализации. 19.09.2016

  • 4

    Пока списки захвата ваших лямбд пусты, вы можете полагаться на тот факт, что такая лямбда распадается на указатель функции.
    Достаточно различать два типа.

    Это следует за минимальным рабочим примером:

    #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; });
    }
    
    19.09.2016
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..