2 votes

Comment implémenter une classe 'Observer', en utilisant std::invoke pour rediriger les appels de fonction ?

J'essaie donc de concevoir une classe Observed<T> qui stocke un T y bool avec la valeur booléenne indiquant si le T a été mise à jour depuis sa dernière définition. Pour démontrer, ma mise en œuvre initiale était quelque chose comme

    template <std::copyable T>
    class Observed
    {
    public:
        Observed() 
            : m_data{}, m_updated{false} {};
        Observed(const T &data) 
            : m_data{data}, m_updated{true} {};

        // .. move, copy, assignment ctors

        // setting the data leaves it 'updated'
        template <typename U>
            requires(std::same_as<T, U>)
        auto write(U &&value) -> void
        {
            m_data = std::forward<U>(value);
            m_updated = true;
        }

        // to test if it has since been modified (i.e. written without reading)
        auto modified() -> bool
        {
            return m_updated;
        }

        // reading the value means it has no longer been updated
        auto value() -> T &
        {
            m_updated = false;
            return m_data;
        }

    private:
        T m_data;
        bool m_updated;
    };

Cependant, le problème est devenu que pour un exemple Observed<std::vector<int>> v Si j'avais besoin d'ajouter un élément au vecteur, j'appellerais v.value().push_back(...) qui dépasserait le write() et, par la suite, la fonction m_updated ne serait pas correctement réglé.

Mon approche un peu loufoque a donc consisté à introduire un call() comme suit :

    template <std::copyable T>
    class Observed
    {
    public:
        // ctors
        Observed() 
            : m_data{}, m_updated{false} {};
        Observed(const T &data) 
            : m_data{data}, m_updated{true} {};

        // the new 'call' function
        template<typename Func, typename... Ts>
        auto call(Func&& func, Ts... args) -> std::invoke_result_t<Func, Ts...>
        {
            m_updated = true;
            return std::invoke(func, m_data, args...);
        }

        // .. rest of class

    private:
        T m_data;
        bool m_updated;
    };

afin que vous puissiez écrire v.call(&std::vector<int>::push_back, 1) .

Cependant, cela ne fonctionne pas vraiment et le débogage est un véritable cauchemar. Je me demandais si quelqu'un pouvait y jeter un coup d'œil et m'aider à obtenir la bonne fonctionnalité. J'espère que mon code ci-dessus a montré suffisamment mon intention avec la classe.

Je ne comprends pas très bien non plus comment former le pointeur de fonction vers std::vector<T>::push_back étant donné qu'il prend un allocateur comme second argument du modèle.

Merci.

1voto

max66 Points 4276

Qu'en est-il de deux value() méthodes ?

A const un seul, uniquement pour la lecture

    T const & read_value() const
    {
        m_updated = false;
        return m_data;
    }

et une seconde, pour modifier le contenu

    T & write_value()
    {
        m_updated = true;
        return m_data;
    }

0voto

Jonah F Points 41

J'ai trouvé une solution ! (Je pense !)

Le premier problème est donc que, étant donné vector<T>::push_back est surchargé, lorsque nous passons le point de fonction à call nous devons le convertir au type exact de pointeur de fonction que nous voulons, c'est-à-dire

void (std::vector<int>::*f_ptr)(const int&) = &std::vector<int>::push_back;

et nous pouvons ensuite mettre en œuvre le call() comme suit :

template<typename Ret, typename... Args>
auto call(Ret (T::*func)(Args...), Args... args) 
    -> std::invoke_result_t<Ret(T::*)(Args...), T, Args...>
{
    m_updated = true;
    return std::invoke(func, m_data, args...);
}

Toutefois, cela pose le problème suivant : si T de la classe d'origine n'est pas une classe elle-même, disons T = int puis T::* entraîne une erreur de compilation. Si quelqu'un a des idées sur la façon de résoudre ce problème, n'hésitez pas à en suggérer. SFINAE ?

Edit : trouvé une solution - modéliser le pointeur de fonction sur U

// if you want to update the value via a class method of T
template<typename U, typename Ret, typename... Args, typename... UArgs, 
   std::enable_if<!std::is_integral<U>::value, bool>::type = true
>
auto call(Ret (U::*func)(Args...), UArgs... args) 
    -> std::invoke_result_t<Ret(U::*)(Args...),  U, UArgs...>
{
    return std::invoke(func, m_data, args...);
}

Puis une version améliorée à l'aide de concepts :

template<typename Ret, typename... Args, typename U>
requires(std::same_as<T, U> && !std::is_integral_v<U>)
auto call(Ret (U::*func)(Args...), Args... args) 
    -> std::invoke_result_t<Ret(U::*)(Args...),  U, Args...>
{
    return std::invoke(func, m_data, args...);
}

https://godbolt.org/z/98ffE39Te

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X