Не очень понимаю, как на модных плюсиках сделать такую шляпу
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 21.09.23 12:47
Оценка:
Здравствуйте!

Есть интерфейс, с какими-то виртуальными методами. Есть какие-то реализации этого интерфейса.

Есть отдельная реализация, которая хранит в себе указатели на несколько реализаций того же интерфейса, и каждый вызов метода через эту мульти реализацию должен вызывать этот метод через все хранящиеся указатели на другие реализации.

При этом, если метод что-то возвращает, то за результат берём вызов через первый хранящийся указатель на интерфейс, а остальное игнорируем

  Как-то так
#pragma once

#include <iostream>
#include <memory>


struct IDelegationTest
{

    virtual bool getter() = 0;
    virtual bool setter(bool b) = 0;
    virtual void jobImpl(bool b, int i) = 0;
    virtual void jobImpl(bool b, int i, std::string s) = 0; // !!! А что будет с этой перегрузкой?
    virtual int jobImpl2(bool b, int i) = 0;

};


struct DelegationTestImpl : public IDelegationTest
{
    int n = 0;

    virtual bool getter() override
    {
        std::cout << "DelegationTestImpl::getter(), n: " << n << "\n";
        return true;
    }

    virtual bool setter(bool b) override
    {
        std::cout << "DelegationTestImpl::setter(bool b), b: " << b << ", n: " << n << "\n";
        return true;
    }

    virtual void jobImpl(bool b, int i) override
    {
        std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
    }

    virtual int jobImpl2(bool b, int i) override
    {
        std::cout << "DelegationTestImpl::jobImpl2(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
        return n;
    }

};


struct DelegatorImpl : public IDelegationTest
{
    std::vector< std::shared_ptr<IDelegationTest> > impls;

    template <typename Return, typename... Args>
    Return delegateCall( Return(IDelegationTest::*func)(Args...), Args...)
    {
        // !!! Тут assert на тему impls не должен быть пустым

        std::vector<std::shared_ptr>::const_iterator it = impls.begin();

        // !!! Или assert тут по it!=impls.end();

        // Получаем результат
        auto res = it->func(Args...); ++it;

        for(; it!=impls.end(); ++it)
        {
            // Вызываем метод для всех экземпляров имплементаций
            it->func(Args...); ++it;
        }

        return res;
    }

    // 
    template <typename... Args>
    void delegateCall( void(IDelegationTest::*func)(Args...), Args...)
    {
        std::vector<std::shared_ptr>::const_iterator it = impls.begin();
        for(; it!=impls.end(); ++it)
        {
            // Вызываем метод для всех экземпляров имплементаций
            it->func(Args...); ++it;
        }
    }

    // !!! Тут ещё проблема, а что будет с перегруженными виртуальными методами?


    virtual bool getter() override
    {
        return delegateCall(getter);
    }

    virtual bool setter(bool b)
    {
        return delegateCall(setter, b);
    }

    virtual void jobImpl(bool b, int i)
    {
        delegateCall(jobImpl, b, i);
    }

    virtual void jobImpl2(bool b, int i)
    {
        return delegateCall(jobImpl2, b, i);
    }

};


inline
std::shared_ptr<IDelegationTest> makeDelegator()
{
    DelegatorImpl impl;

    impl.impls.emplace_back(std::make_shared<DelegationTestImpl>(1));
    impl.impls.emplace_back(std::make_shared<DelegationTestImpl>(2));

    // !!! Тут надо создать экземпляр DelegatorImpl, но вернуть надо std::make_shared<IDelegationTest>
    return std::make_shared<IDelegationTest>(impl);
}


int main()
{
    std::shared_ptr<IDelegationTest> pDelegator = makeDelegator();

    pDelegator->getter();
    pDelegator->setter(true);
    pDelegator->jobImpl(false, 3);
    pDelegator->jobImpl2(true, 5);
}
Маньяк Робокряк колесит по городу
Re: Не очень понимаю, как на модных плюсиках сделать такую шляпу
От: kov_serg Россия  
Дата: 21.09.23 13:36
Оценка: +2
Здравствуйте, Marty, Вы писали:

M>При этом, если метод что-то возвращает, то за результат берём вызов через первый хранящийся указатель на интерфейс, а остальное игнорируем

А вопрос-то в чём?
Пока не реализуешь все абстрактные методы класс не создать.
если есть перегрузки, придётся указывать сигнатуру метода.
если хочется создавать копию, опишите это явно.
или создавайте динамически и добавляйте уже туда реализации.
Re: /
От: Sm0ke Россия ksi
Дата: 21.09.23 17:31
Оценка: 6 (1)
Пофиксил: https://godbolt.org/z/czfjG44jv

#include <iostream>
#include <memory>
#include <vector>

struct IDelegationTest
{
    virtual bool getter() = 0;
    virtual bool setter(bool b) = 0;
    virtual void jobImpl(bool b, int i) = 0;
    virtual int jobImpl2(bool b, int i) = 0;

};

struct DelegationTestImpl : public IDelegationTest
{
    int n = 0;

    DelegationTestImpl(int p) : n{p} {}

    bool getter() override
    {
        std::cout << "DelegationTestImpl::getter(), n: " << n << "\n";
        return true;
    }

    bool setter(bool b) override
    {
        std::cout << "DelegationTestImpl::setter(bool b), b: " << b << ", n: " << n << "\n";
        return true;
    }

    void jobImpl(bool b, int i) override
    {
        std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
    }

    int jobImpl2(bool b, int i) override
    {
        std::cout << "DelegationTestImpl::jobImpl2(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
        return n;
    }

};

struct DelegatorImpl : public IDelegationTest
{
  using container_type = std::vector< std::shared_ptr<IDelegationTest> >;
    
    // data
    container_type impls;

    template <typename Return, typename... Args>
    Return delegateCall( Return(IDelegationTest::*func)(Args...), Args... args)
    {
        // !!! Тут assert на тему impls не должен быть пустым

        container_type::const_iterator it = impls.begin();

        // !!! Или assert тут по it!=impls.end();

        // Получаем результат
        auto res = ((**it).*func)(args...); ++it;

        for(; it!=impls.end(); ++it)
        {
            // Вызываем метод для всех экземпляров имплементаций
            ((**it).*func)(args...);
        }

        return res;
    }

    // 
    template <typename... Args>
    void delegateCall( void(IDelegationTest::*func)(Args...), Args... args)
    {
        container_type::const_iterator it = impls.begin();
        for(; it!=impls.end(); ++it)
        {
            ((**it).*func)(args...);
        }
    }

    bool getter() override
    {
        return delegateCall(&IDelegationTest::getter);
    }

    bool setter(bool b) override
    {
        return delegateCall(&IDelegationTest::setter, b);
    }

    void jobImpl(bool b, int i) override
    {
        delegateCall(&IDelegationTest::jobImpl, b, i);
    }

    int jobImpl2(bool b, int i) override
    {
        return delegateCall(&IDelegationTest::jobImpl2, b, i);
    }

};


inline
std::shared_ptr<IDelegationTest> makeDelegator()
{
  std::shared_ptr<DelegatorImpl> ret = std::make_shared<DelegatorImpl>();
    ret->impls.emplace_back(std::make_shared<DelegationTestImpl>(1));
    ret->impls.emplace_back(std::make_shared<DelegationTestImpl>(2));

    return std::move(ret);
}


int main()
{
    std::shared_ptr<IDelegationTest> pDelegator = makeDelegator();

    pDelegator->getter();
    pDelegator->setter(true);
    pDelegator->jobImpl(false, 3);
    pDelegator->jobImpl2(true, 5);
}

Output

Program returned: 0
DelegationTestImpl::getter(), n: 1
DelegationTestImpl::getter(), n: 2
DelegationTestImpl::setter(bool b), b: 1, n: 1
DelegationTestImpl::setter(bool b), b: 1, n: 2
DelegationTestImpl::jobImpl(bool b, int i), b: 0, i: 3, n: 1
DelegationTestImpl::jobImpl(bool b, int i), b: 0, i: 3, n: 2
DelegationTestImpl::jobImpl2(bool b, int i), b: 1, i: 5, n: 1
DelegationTestImpl::jobImpl2(bool b, int i), b: 1, i: 5, n: 2

Re[2]: Не очень понимаю, как на модных плюсиках сделать такую шляпу
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 21.09.23 18:02
Оценка:
Здравствуйте, kov_serg, Вы писали:

M>>При этом, если метод что-то возвращает, то за результат берём вызов через первый хранящийся указатель на интерфейс, а остальное игнорируем

_>А вопрос-то в чём?

Ну, я с вариадиками на "Вы", не очень было понятно. Плюс редко приходится пользоваться указателями на функции, тем более на виртуальные методы, в купе с shared_ptr. Куча мелких деталей, в которых я не очень ориентируюсь за редкостью использования.


_>Пока не реализуешь все абстрактные методы класс не создать.


Ну, это-то ясно


_>если есть перегрузки, придётся указывать сигнатуру метода.


Да, видимо так, хотя хотелось бы без сигнатуры


_>если хочется создавать копию, опишите это явно.

_>или создавайте динамически и добавляйте уже туда реализации.

тут не понял
Маньяк Робокряк колесит по городу
Re: Не очень понимаю, как на модных плюсиках сделать такую шляпу
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 21.09.23 18:05
Оценка:
Здравствуйте, Marty, Вы писали:

Код коллеги
Автор: Sm0ke
Дата: 21.09.23
, потестил, как оно с перегрузкой

  Скрытый текст
#pragma once

#include <iostream>
#include <memory>

// http://www.gotw.ca/publications/mill18.htm


#include <vector>

struct IDelegationTest
{
    virtual ~IDelegationTest() {}

    virtual bool getter() = 0;
    virtual bool setter(bool b) = 0;
    virtual void jobImpl(bool b, int i) = 0;
    virtual int jobImpl2(bool b, int i) = 0;
    virtual int jobImpl2(bool b, std::string s) = 0;

};

struct DelegationTestImpl : public IDelegationTest
{
    int n = 0;

    DelegationTestImpl(int p) : n{p} {}

    bool getter() override
    {
        std::cout << "DelegationTestImpl::getter(), n: " << n << "\n";
        return true;
    }

    bool setter(bool b) override
    {
        std::cout << "DelegationTestImpl::setter(bool b), b: " << b << ", n: " << n << "\n";
        return true;
    }

    void jobImpl(bool b, int i) override
    {
        std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
    }

    int jobImpl2(bool b, int i) override
    {
        std::cout << "DelegationTestImpl::jobImpl2(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
        return n;
    }

    int jobImpl2(bool b, std::string s) override
    {
        std::cout << "DelegationTestImpl::jobImpl2(bool b, std::string s), b: " << b << ", s: " << s << ", n: " << n << "\n";
        return n;
    }


};

struct DelegatorImpl : public IDelegationTest
{
  using container_type = std::vector< std::shared_ptr<IDelegationTest> >;
    
    // data
    container_type impls;

    template <typename Return, typename... Args>
    Return delegateCall( Return(IDelegationTest::*func)(Args...), Args... args)
    {
        // !!! Тут assert на тему impls не должен быть пустым

        container_type::const_iterator it = impls.begin();

        // !!! Или assert тут по it!=impls.end();

        // Получаем результат
        auto res = ((**it).*func)(args...); ++it;

        for(; it!=impls.end(); ++it)
        {
            // Вызываем метод для всех экземпляров имплементаций
            ((**it).*func)(args...);
        }

        return res;
    }

    // 
    template <typename... Args>
    void delegateCall( void(IDelegationTest::*func)(Args...), Args... args)
    {
        container_type::const_iterator it = impls.begin();
        for(; it!=impls.end(); ++it)
        {
            ((**it).*func)(args...);
        }
    }

    bool getter() override
    {
        return delegateCall(&IDelegationTest::getter);
    }

    bool setter(bool b) override
    {
        return delegateCall(&IDelegationTest::setter, b);
    }

    void jobImpl(bool b, int i) override
    {
        delegateCall(&IDelegationTest::jobImpl, b, i);
    }

    int jobImpl2(bool b, int i) override
    {
        return delegateCall(static_cast<int(IDelegationTest::*)(bool, int)>(&IDelegationTest::jobImpl2), b, i);
    }

    int jobImpl2(bool b, std::string s) override
    {
        return delegateCall(static_cast<int(IDelegationTest::*)(bool, std::string)>(&IDelegationTest::jobImpl2), b, s);
    }

};


inline
std::shared_ptr<IDelegationTest> makeDelegator()
{
  std::shared_ptr<DelegatorImpl> ret = std::make_shared<DelegatorImpl>();
    ret->impls.emplace_back(std::make_shared<DelegationTestImpl>(1));
    ret->impls.emplace_back(std::make_shared<DelegationTestImpl>(2));

    return std::move(ret);
}


int main()
{
    std::shared_ptr<IDelegationTest> pDelegator = makeDelegator();

    pDelegator->getter();
    pDelegator->setter(true);
    pDelegator->jobImpl(false, 3);
    pDelegator->jobImpl2(true, 5);
    pDelegator->jobImpl2(true, "str");
}


Без явного указания прототипа с перегрузками никак не решить вопрос? Хочется, чтобы оно само догадалось
Маньяк Робокряк колесит по городу
Re: Не очень понимаю, как на модных плюсиках сделать такую ш
От: rg45 СССР  
Дата: 21.09.23 20:27
Оценка: 4 (1)
Здравствуйте, Marty, Вы писали:

M>Здравствуйте!


M>Есть интерфейс, с какими-то виртуальными методами. Есть какие-то реализации этого интерфейса.


M>Есть отдельная реализация, которая хранит в себе указатели на несколько реализаций того же интерфейса, и каждый вызов метода через эту мульти реализацию должен вызывать этот метод через все хранящиеся указатели на другие реализации.


M>При этом, если метод что-то возвращает, то за результат берём вызов через первый хранящийся указатель на интерфейс, а остальное игнорируем


Ну, вот как-то так. Без явного указания прототипа с перегрузками, как ты и хотел. Единственная небольшая шероховатость — первый обработчик всегда вызывается последним (потому, что именно его результат возвращается). Но эта мелочь легко устраняется добавлением еще одного метода делегирования (аналогичного doDelegate):

http://coliru.stacked-crooked.com/a/3b8ca43b7a409e5f

#include <concepts>
#include <iostream>
#include <memory>
#include <vector>

struct IDelegationTest
{

   virtual bool getter() = 0;
   virtual bool setter(bool b) = 0;
   virtual void jobImpl(bool b, int i) = 0;
   virtual void jobImpl(bool b, int i, const std::string& s) = 0;
   virtual int jobImpl2(bool b, int i) = 0;

};

struct DelegationTestImpl : public IDelegationTest
{
   int n = 0;

   DelegationTestImpl() = default;
   explicit DelegationTestImpl(int n) : n(n) {}

   virtual bool getter() override
   {
      std::cout << "DelegationTestImpl::getter(), n: " << n << "\n";
      return true;
   }

   virtual bool setter(bool b) override
   {
      std::cout << "DelegationTestImpl::setter(bool b), b: " << b << ", n: " << n << "\n";
      return true;
   }

   virtual void jobImpl(bool b, int i) override
   {
      std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
   }

   virtual void jobImpl(bool b, int i, const std::string& s) override
   {
      std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", s: " << s << ", n: " << n << "\n";
   }

   virtual int jobImpl2(bool b, int i) override
   {
      std::cout << "DelegationTestImpl::jobImpl2(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
      return n;
   }

};

template <typename F>
concept DelegatorAgent = requires (F f, IDelegationTest& t)
{
   f(t);
};

template <typename T>
concept DelegationTestPtr = std::convertible_to<std::decay_t<T>, std::shared_ptr<IDelegationTest>>;

class Delegator : public IDelegationTest
{
   template <DelegatorAgent F>
   decltype(auto) doDelegate(F&& f)
   {
      for (auto&& impl : m_tail)
      {
         f(*impl);
      }
      return f(*m_head);
   }

public:

   template <DelegationTestPtr T, DelegationTestPtr...X>
   explicit Delegator(T&& head, X&&...tail)
      : m_head(std::forward<T>(head)), m_tail{ std::forward<X>(tail)...} {}

   bool getter() override { return doDelegate([&](IDelegationTest& t){ return t.getter(); }); }
   bool setter(bool b) override { return doDelegate([&](IDelegationTest& t) { return t.setter(b); }); }
   void jobImpl(bool b, int i) override { doDelegate([&](IDelegationTest& t) { t.jobImpl(b, i); }); }
   void jobImpl(bool b, int i, const std::string& s) override { doDelegate([&](IDelegationTest& t) { t.jobImpl(b, i, s); }); }
   int jobImpl2(bool b, int i) override { return doDelegate([&](IDelegationTest& t) { return t.jobImpl2(b, i); }); }

private:

   using ImplPtr = std::shared_ptr<IDelegationTest>;

   const ImplPtr m_head;
   const std::vector<ImplPtr> m_tail;
};

int main()
{
   Delegator delegator {
      std::make_shared<DelegationTestImpl>(1),
      std::make_shared<DelegationTestImpl>(2),
      std::make_shared<DelegationTestImpl>(3),
   };

   delegator.getter();
   delegator.setter(true);
   delegator.jobImpl(false, 3);
   delegator.jobImpl2(true, 5);
}
--
Отредактировано 21.09.2023 20:31 rg45 . Предыдущая версия .
Re[2]: Не очень понимаю, как на модных плюсиках сделать такую ш
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 21.09.23 20:46
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну, вот как-то так. Без явного указания прототипа с перегрузками, как ты и хотел. Единственная небольшая шероховатость — первый обработчик всегда вызывается последним (потому, что именно его результат возвращается). Но эта мелочь легко устраняется добавлением еще одного метода делегирования (аналогичного doDelegate):


Очень интересно, но ничего не понятно

Без концептом никак, я правильно понимаю?

Ладно, я пожалуй лучше поживу пока с указанием прототипа для перегрузок
Маньяк Робокряк колесит по городу
Re[3]: Не очень понимаю, как на модных плюсиках сделать такую ш
От: rg45 СССР  
Дата: 21.09.23 20:51
Оценка: 6 (1)
Здравствуйте, Marty, Вы писали:

M>Без концептом никак, я правильно понимаю?


Да вообще без проблем, здесь они только создают типовую безопасность, но принципиальной роли не играют, просто удаляются и все. Обрати внимание, следующий пример скомпилирован в режиме C++14.

http://coliru.stacked-crooked.com/a/601ce249beca8a94

#include <iostream>
#include <memory>
#include <vector>

struct IDelegationTest
{

   virtual bool getter() = 0;
   virtual bool setter(bool b) = 0;
   virtual void jobImpl(bool b, int i) = 0;
   virtual void jobImpl(bool b, int i, const std::string& s) = 0;
   virtual int jobImpl2(bool b, int i) = 0;

};


struct DelegationTestImpl : public IDelegationTest
{
   int n = 0;

   DelegationTestImpl() = default;
   explicit DelegationTestImpl(int n) : n(n) {}

   virtual bool getter() override
   {
      std::cout << "DelegationTestImpl::getter(), n: " << n << "\n";
      return true;
   }

   virtual bool setter(bool b) override
   {
      std::cout << "DelegationTestImpl::setter(bool b), b: " << b << ", n: " << n << "\n";
      return true;
   }

   virtual void jobImpl(bool b, int i) override
   {
      std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
   }

   virtual void jobImpl(bool b, int i, const std::string& s) override
   {
      std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", s: " << s << ", n: " << n << "\n";
   }

   virtual int jobImpl2(bool b, int i) override
   {
      std::cout << "DelegationTestImpl::jobImpl2(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
      return n;
   }

};

class Delegator : public IDelegationTest
{
   template <typename F>
   decltype(auto) doDelegate(F&& f)
   {
      for (auto&& impl : m_tail)
      {
         f(*impl);
      }
      return f(*m_head);
   }

public:

   template <typename T, typename...X>
   explicit Delegator(T&& head, X&&...tail)
      : m_head(std::forward<T>(head)), m_tail{ std::forward<X>(tail)...} {}

   bool getter() override { return doDelegate([&](IDelegationTest& t){ return t.getter(); }); }
   bool setter(bool b) override { return doDelegate([&](IDelegationTest& t) { return t.setter(b); }); }
   void jobImpl(bool b, int i) override { doDelegate([&](IDelegationTest& t) { t.jobImpl(b, i); }); }
   void jobImpl(bool b, int i, const std::string& s) override { doDelegate([&](IDelegationTest& t) { t.jobImpl(b, i, s); }); }
   int jobImpl2(bool b, int i) override { return doDelegate([&](IDelegationTest& t) { return t.jobImpl2(b, i); }); }

private:

   using ImplPtr = std::shared_ptr<IDelegationTest>;

   const ImplPtr m_head;
   const std::vector<ImplPtr> m_tail;
};

int main()
{
   Delegator delegator {
      std::make_shared<DelegationTestImpl>(1),
      std::make_shared<DelegationTestImpl>(2),
      std::make_shared<DelegationTestImpl>(3),
   };

   delegator.getter();
   delegator.setter(true);
   delegator.jobImpl(false, 3);
   delegator.jobImpl2(true, 5);
}
--
Re[3]: Не очень понимаю, как на модных плюсиках сделать таку
От: rg45 СССР  
Дата: 21.09.23 21:37
Оценка:
Здравствуйте, Marty, Вы писали:

R>>Единственная небольшая шероховатость — первый обработчик всегда вызывается последним (потому, что именно его результат возвращается). Но эта мелочь легко устраняется добавлением еще одного метода делегирования (аналогичного doDelegate):


Я выше упоминал о некрасивости, что первый обработчик вызыается последним. На самом деле у этой проблемы есть более элегантное решение, чем добаление еще одной перегрузки метода doDelegate. Исправить последовательность вызовов можно, использовав идиому RAII (см. класс AtExit и его использование в doDelegate). Следующий пример уже работает в точном соответствии с требованиями и в правильной последовательности, при этом все делегирование выполняется одним общим методом (doDelegate):

http://coliru.stacked-crooked.com/a/d66ee722f822d4fc

#include <functional>
#include <iostream>
#include <memory>
#include <vector>

struct IDelegationTest
{

   virtual bool getter() = 0;
   virtual bool setter(bool b) = 0;
   virtual void jobImpl(bool b, int i) = 0;
   virtual void jobImpl(bool b, int i, const std::string& s) = 0;
   virtual int jobImpl2(bool b, int i) = 0;

};


struct DelegationTestImpl : public IDelegationTest
{
   int n = 0;

   DelegationTestImpl() = default;
   explicit DelegationTestImpl(int n) : n(n) {}

   virtual bool getter() override
   {
      std::cout << "DelegationTestImpl::getter(), n: " << n << "\n";
      return true;
   }

   virtual bool setter(bool b) override
   {
      std::cout << "DelegationTestImpl::setter(bool b), b: " << b << ", n: " << n << "\n";
      return true;
   }

   virtual void jobImpl(bool b, int i) override
   {
      std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
   }

   virtual void jobImpl(bool b, int i, const std::string& s) override
   {
      std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", s: " << s << ", n: " << n << "\n";
   }

   virtual int jobImpl2(bool b, int i) override
   {
      std::cout << "DelegationTestImpl::jobImpl2(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
      return n;
   }

};

struct AtExit
{
    std::function<void()> guard;
    ~AtExit() { guard(); }
};

class Delegator : public IDelegationTest
{
   template <typename F>
   decltype(auto) doDelegate(F&& f)
   {
      const AtExit callTail {[&]
      {
         for (auto&& impl : m_tail)
         {
            f(*impl);
         }
      }};     
      return f(*m_head);
   }

public:

   template <typename T, typename...X>
   explicit Delegator(T&& head, X&&...tail)
      : m_head(std::forward<T>(head)), m_tail{ std::forward<X>(tail)...} {}

   bool getter() override { return doDelegate([&](IDelegationTest& t){ return t.getter(); }); }
   bool setter(bool b) override { return doDelegate([&](IDelegationTest& t) { return t.setter(b); }); }
   void jobImpl(bool b, int i) override { doDelegate([&](IDelegationTest& t) { t.jobImpl(b, i); }); }
   void jobImpl(bool b, int i, const std::string& s) override { doDelegate([&](IDelegationTest& t) { t.jobImpl(b, i, s); }); }
   int jobImpl2(bool b, int i) override { return doDelegate([&](IDelegationTest& t) { return t.jobImpl2(b, i); }); }

private:

   using ImplPtr = std::shared_ptr<IDelegationTest>;

   const ImplPtr m_head;
   const std::vector<ImplPtr> m_tail;
};

int main()
{
   Delegator delegator {
      std::make_shared<DelegationTestImpl>(1),
      std::make_shared<DelegationTestImpl>(2),
      std::make_shared<DelegationTestImpl>(3),
   };

   delegator.getter();
   delegator.setter(true);
   delegator.jobImpl(false, 3);
   delegator.jobImpl2(true, 5);
}
--
Отредактировано 21.09.2023 21:38 rg45 . Предыдущая версия .
Re[4]: Не очень понимаю, как на модных плюсиках сделать таку
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 21.09.23 21:46
Оценка:
Здравствуйте, rg45, Вы писали:

R>>>Единственная небольшая шероховатость — первый обработчик всегда вызывается последним (потому, что именно его результат возвращается). Но эта мелочь легко устраняется добавлением еще одного метода делегирования (аналогичного doDelegate):


R>Я выше упоминал о некрасивости, что первый обработчик вызыается последним. На самом деле у этой проблемы есть более элегантное решение, чем добаление еще одной перегрузки метода doDelegate. Исправить последовательность вызовов можно, использовав идиому RAII (см. класс AtExit и его использование в doDelegate). Следующий пример уже работает в точном соответствии с требованиями и в правильной последовательности, при этом все делегирование выполняется одним общим методом (doDelegate):


Не очень понятно, зачем ты разделил вектор на m_head и вектор m_tail?
Ради этого цикла?
for (auto&& impl : m_tail)
{
  f(*impl);
}
Маньяк Робокряк колесит по городу
Re[5]: Не очень понимаю, как на модных плюсиках сделать таку
От: rg45 СССР  
Дата: 21.09.23 21:53
Оценка: +1
Здравствуйте, Marty, Вы писали:

M>Не очень понятно, зачем ты разделил вектор на m_head и вектор m_tail?


Это убивает сразу нескольких зайцев. Во-первых, не нужно проверять и делать специальную обработку для пустого множества обработчиков. Во-вторых, для итерирования по коллеции я могу использовать более аккуратный цикл range for вместо циклов с использованием итераторов или счетчиков цикла. Ну и в-третьих, так лучше видно в коде, что делегатор достает именно из этого элемента. Семантика этого элемента немного отличается от остальных, поэтому лучше его сразу обособить, так код становится проще и яснее.

Ты можешь попробовать, ради эксперимента, слить этот элемент с общей коллекцией. Потом посмотришь и сравнишь. Не забудь при этом сравнить реализации с точки зрения безопасности и обработки ошибок. Только лучше бери сразу последнюю версию: http://coliru.stacked-crooked.com/a/d66ee722f822d4fc.
--
Отредактировано 21.09.2023 21:56 rg45 . Предыдущая версия .
Re[6]: Не очень понимаю, как на модных плюсиках сделать таку
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 21.09.23 21:56
Оценка:
Здравствуйте, rg45, Вы писали:

R>Это убивает сразу нескольких зайцев. Во-первых, не нужно проверять и делать специальную обработку для пустого множества обработчиков. Во-вторых, для итерирования по коллеции я могу использовать более аккуратный цикл range for вместо циклов с использованием итераторов или счетчиков цикла. Ну и в-третьих, так лучше видно в коде, что делегатор достает именно из этого элемента. Семантика этого элемента немного отличается от остальных, поэтому лучше его сразу обособить, так код становится проще и яснее.


Ясно.

А ещё не разжуёшь вот это:
for (auto && impl : m_tail)
Маньяк Робокряк колесит по городу
Re[7]: Не очень понимаю, как на модных плюсиках сделать таку
От: rg45 СССР  
Дата: 21.09.23 22:24
Оценка:
Здравствуйте, Marty, Вы писали:

M>А ещё не разжуёшь вот это:

M>
M>for (auto && impl : m_tail)
M>


Ну impl — это имя переменной цикла, которая, в данном случае, представляет собой ссылку на элемент вектора m_tail. Двойной амперсанд здесь используется как универсальный вариант доступа к контейнерам с различной константностью — если контейнер константный то ссылка на элемент будет константной, а если контейнер неконстантный, то и ссылка будет неконстантной. Хотя в данном случае это и не имеет особого значения, я мог бы вместо auto&& написать const auto& или просто auto и тоже все работало бы, просто по привычке использовал наиболее обобщенную запись. Описанный механизм известен под названием Forwarding references.

А вообще такая форма цикла for — это и есть как раз тот самый range-for, о котором я умоминал выше, и это является аналогом следующей записи:

for (auto it = m_tail.begin(); it != m_tail.end(); ++it)
{
   auto&& impl = *it;
   // . . .
}


А в C++03 полгого аналога вообще не существовало, поскольку там всегда нужно было указывать явно, какой тип итератора ты используешь — константный, или неконстантный.
--
Отредактировано 21.09.2023 22:37 rg45 . Предыдущая версия .
Re[2]: Не очень понимаю, как на модных плюсиках сделать такую шляпу
От: Chorkov Россия  
Дата: 22.09.23 09:13
Оценка: 1 (1)
M>Без явного указания прототипа с перегрузками никак не решить вопрос? Хочется, чтобы оно само догадалось


Если использовать if constexpr, то можно обойтись одним телом:
https://godbolt.org/z/o9esEfzG6

    template <typename Return, typename... Args>
    Return delegateCall( Return(IDelegationTest::*func)(Args...), Args... args)
    {
        if constexpr ( ! std::is_same_v<Return,void>)
        {
            // !!! Тут assert на тему impls не должен быть пустым
        ......

    int jobImpl2(bool b, std::string s) override
    {
        return delegateCall<int,bool,std::string>(&IDelegationTest::jobImpl2, b, s);
    }



Это позволит указывать параметры шаблона, вместо каста функции. (Чуть короче, но плохо читаемо.)


Если разделить вызов на два, то тип функции можно угадывать по аргументам:
https://godbolt.org/z/7Ga1z31z1
Но вызов получается некравивым:
    int jobImpl2(bool b, std::string s) override
    {
        return delegateCall(b, s)(&IDelegationTest::jobImpl2);
    }
Re[4]: Не очень понимаю, как на модных плюсиках сделать таку
От: rg45 СССР  
Дата: 22.09.23 11:07
Оценка:
Здравствуйте, rg45, Вы писали:

R>Я выше упоминал о некрасивости, что первый обработчик вызыается последним. На самом деле у этой проблемы есть более элегантное решение, чем добаление еще одной перегрузки метода doDelegate. Исправить последовательность вызовов можно, использовав идиому RAII (см. класс AtExit и его использование в doDelegate). Следующий пример уже работает в точном соответствии с требованиями и в правильной последовательности, при этом все делегирование выполняется одним общим методом (doDelegate):


R>http://coliru.stacked-crooked.com/a/d66ee722f822d4fc


А теперь, легким движением руки, переходим от динамического полиморфизма к статическому (угу, с концептами):

http://coliru.stacked-crooked.com/a/b0b32792bb074572

#include <functional>
#include <iostream>
#include <memory>
#include <vector>

template <typename T>
concept IDelegationTest = requires(T t, bool b, int i, const std::string& s)
{

   { t.getter() } -> std::same_as<bool>;
   { t.setter(b) } -> std::same_as<bool>;
   { t.jobImpl(b, i) } -> std::same_as<void>;
   { t.jobImpl(b, i, s) } -> std::same_as<void>;
   { t.jobImpl2(b, i) } -> std::same_as<int>;

};

struct DelegationTestImpl
{
   int n = 0;

   DelegationTestImpl() = default;
   explicit DelegationTestImpl(int n) : n(n)
   {
      static_assert(IDelegationTest<DelegationTestImpl>);
   }

   bool getter()
   {
      std::cout << "DelegationTestImpl::getter(), n: " << n << "\n";
      return true;
   }

   bool setter(bool b)
   {
      std::cout << "DelegationTestImpl::setter(bool b), b: " << b << ", n: " << n << "\n";
      return true;
   }

   void jobImpl(bool b, int i)
   {
      std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
   }

   void jobImpl(bool b, int i, const std::string& s)
   {
      std::cout << "DelegationTestImpl::jobImpl(bool b, int i), b: " << b << ", i: " << i << ", s: " << s << ", n: " << n << "\n";
   }

   int jobImpl2(bool b, int i)
   {
      std::cout << "DelegationTestImpl::jobImpl2(bool b, int i), b: " << b << ", i: " << i << ", n: " << n << "\n";
      return n;
   }

};

struct AtExit
{
    std::function<void()> guard;
    ~AtExit() { guard(); }
};

template <IDelegationTest Head, IDelegationTest...Tail>
class Delegator
{
public:

   explicit Delegator(Head&& head, Tail&&...tail)
      : m_head(std::forward<Head>(head))
      , m_tail{ std::forward<Tail>(tail)...}
      {
         static_assert(IDelegationTest<Delegator>);
      }

   bool getter() { return doDelegate([&](IDelegationTest auto && t){ return t.getter(); }); }
   bool setter(bool b) { return doDelegate([&](IDelegationTest auto && t) { return t.setter(b); }); }
   void jobImpl(bool b, int i) { doDelegate([&](IDelegationTest auto && t) { t.jobImpl(b, i); }); }
   void jobImpl(bool b, int i, const std::string& s) { doDelegate([&](IDelegationTest auto && t) { t.jobImpl(b, i, s); }); }
   int jobImpl2(bool b, int i) { return doDelegate([&](IDelegationTest auto && t) { return t.jobImpl2(b, i); }); }

private:

   template <typename F>
   decltype(auto) doDelegate(F&& f)
   {
      const AtExit callTail {[&]
      {
         std::apply([&](IDelegationTest auto&&...t){ (f(t), ...); }, m_tail);
      }};
      return f(m_head);
   }

   Head m_head;
   std::tuple<Tail...> m_tail;
};

int main()
{
   Delegator delegator {
      DelegationTestImpl(1),
      DelegationTestImpl(2),
      Delegator {
         DelegationTestImpl(3),
         DelegationTestImpl(4)
      }
   };

   delegator.getter();
   delegator.setter(true);
   delegator.jobImpl(false, 3);
   delegator.jobImpl2(true, 5);
}
--
Отредактировано 22.09.2023 11:17 rg45 . Предыдущая версия .
Re[5]: Не очень понимаю, как на модных плюсиках сделать таку
От: Sm0ke Россия ksi
Дата: 22.09.23 11:52
Оценка:
Здравствуйте, rg45, Вы писали:

R>А теперь, легким движением руки, переходим от динамического полиморфизма к статическому (угу, с концептами):


R>http://coliru.stacked-crooked.com/a/b0b32792bb074572


Я так понимаю для TS нужна именно виртуальность. Чтобы была возможность определить impl класс в run-time
Re[4]: Не очень понимаю, как на модных плюсиках сделать таку
От: Sm0ke Россия ksi
Дата: 22.09.23 11:55
Оценка:
Здравствуйте, rg45, Вы писали:

R>http://coliru.stacked-crooked.com/a/d66ee722f822d4fc


Можно ли как-то обойтись без std::function() ?
Я так понимаю в нём накладные расходы на дополнительный virtual-call, хотя может зависеть от реализации std, но всё же

Что про вызов в RAII — тут можно получить exception из деструктора ...
Отредактировано 22.09.2023 11:57 Sm0ke . Предыдущая версия .
Re[6]: Не очень понимаю, как на модных плюсиках сделать таку
От: rg45 СССР  
Дата: 22.09.23 11:57
Оценка:
Здравствуйте, Sm0ke, Вы писали:

S>Я так понимаю для TS нужна именно виртуальность. Чтобы была возможность определить impl класс в run-time


Все может быть, конечно, я не спорю. В то же время я видел достаточно много примеров, когда динамический полиморфизм использовался там, где за глаза хватало статического — просто по причине консервативности мышления.
--
Re[5]: Не очень понимаю, как на модных плюсиках сделать таку
От: rg45 СССР  
Дата: 22.09.23 12:06
Оценка: 10 (1)
Здравствуйте, Sm0ke, Вы писали:

S>Можно ли как-то обойтись без std::function() ?


Можно, конечно. в С++14 и раньше — это достигается ценой добавления дополнительной функции:

http://coliru.stacked-crooked.com/a/e918d16f33d820de

template <typename Guard>
struct AtExit
{
    Guard guard;
    ~AtExit() { guard(); }
};

template <typename Guard>
AtExit<Guard> MakeGuard(Guard&& guard) { return {std::forward<Guard>(guard)}; }

// . . .
      const auto callTail = MakeGuard ([&]
      {
// . . .


Начиная с С++17, можно и без этой дополнительной фунции — просто класс AtExit делается шаблонным и дальше его можно будет использовать без явной спецификации шаблонного параметра, благодаря CTAD:

http://coliru.stacked-crooked.com/a/9d6ba61b6f3e46cb

template <typename Guard>
struct AtExit
{
    Guard guard;
    ~AtExit() { guard(); }
};
template <typename T>
AtExit(T&&) -> AtExit<T>;

// . . .
      const AtExit callTail = {[&]
      {
// . . .
--
Отредактировано 22.09.2023 12:13 rg45 . Предыдущая версия .
Re[4]: Не очень понимаю, как на модных плюсиках сделать таку
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 22.09.23 13:40
Оценка: +1
Здравствуйте, rg45, Вы писали:

R>
R>   template <typename T, typename...X>
R>   explicit Delegator(T&& head, X&&...tail)
R>      : m_head(std::forward<T>(head)), m_tail{ std::forward<X>(tail)...} {}

R>   bool getter() override { return doDelegate([&](IDelegationTest& t){ return t.getter(); }); }
R>   bool setter(bool b) override { return doDelegate([&](IDelegationTest& t) { return t.setter(b); }); }
R>   void jobImpl(bool b, int i) override { doDelegate([&](IDelegationTest& t) { t.jobImpl(b, i); }); }
R>   void jobImpl(bool b, int i, const std::string& s) override { doDelegate([&](IDelegationTest& t) { t.jobImpl(b, i, s); }); }
R>   int jobImpl2(bool b, int i) override { return doDelegate([&](IDelegationTest& t) { return t.jobImpl2(b, i); }); }
R>



Кстати, тут везде можно return использовать, даже с void, непонятно, зачем ты его опустил в этих случаях
Маньяк Робокряк колесит по городу
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.