cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

`operator->*` для типов стандартной библиотеки, имеющих `operator->` #529

Open pavelkryukov opened 1 year ago

pavelkryukov commented 1 year ago

Предложение Определить operator->* для того, что в стандартной библиотеке умеет в operator->. Навскидку вспоминаю unique_ptr, shared_ptr, optional, итераторы.

template<typename R>
R& operator->*(R T::*member) const
{
    return this->operator->()->*member;
}

template<typename R, typename ... Args>
auto operator->*(R (T::*member)(Args...)) const
{
    return [ptr=this->operator->(), member](Args&& ... args) { return (ptr->*member)(std::forward(args)...); };
}

template<typename R, typename ... Args>
auto operator->*(R (T::*member)(Args...) const) const
{
    return [ptr=this->operator->(), member](Args&& ... args) { return (ptr->*member)(std::forward(args)...); };
}

Как я догадываюсь, это не было сделано изначально из-за отсутствия лямбд и вариабельных шаблонов, без них решение весьма громоздко [1].

Область применения Если к разным полям хранимой по smart-pointer/итераторам структуры применяются одни и те же алгоритмы, то хочется убрать повторяющийся код и использовать одну (мета-)функцию, принимающую pointer-to-member аргументом. Схожий пример с методами – мультиплицирующий адаптер [2]:

class Interface
{
public:
    virtual void foo(int x) = 0;
    virtual void bar(int y, int z) = 0;
    // много других функций
};

class Adapter : public Interface
{
    std::list<std::shared_ptr<Interface>> children;

public:
    void foo(int x) override
    {
        for (auto& e : children)
            e->foo(x);
    }

    void bar(int y, int z) override
    {
        for (auto& e : children)
            e->bar(y, z);
    }

   // и так далее...
};

// то же, но немного проще:
class Adapter2 : public Interface
{
    std::list<std::shared_ptr<Interface>> children;

    template<auto f, typename ... Args>
    void adapt(Args&& ... args)
    {
         for (auto& e : children) {
             //(e->*f)(std::forward<Args>(args)...); // ошибка компиляции!
             (*e.*f)(std::forward<Args>(args)...); // а это работает
         }
    }

public:
    void foo(int x) override { adapt<&Interface::foo>(x); }
    void bar(int y, int z) override { adapt<&Interface::bar>(y, z); }
    // и так далее, но уже в два раза меньше кода
};

Прожить с (*e.*f) вместо (e->*f) можно, но хотелось бы полной симметрии с C-указателями.

Ссылки

  1. Имплементация такой перегрузки в C++98 от Scott Meyers: https://www.aristeia.com/Papers/DDJ_Oct_1999.pdf
  2. Пример для unique_ptr: https://godbolt.org/z/oY6joezca