Можно ли определить тип поля класса на основе значения, присвоенного ему в функции самого класса?

#c #metaprogramming #template-meta-programming #c 20 #coroutine

Вопрос:

Можно ли определить тип поля класса на основе значения, присвоенного ему в функции самого класса?

Я пишу оболочку для библиотеки ozo, которая основана на boost.Библиотека Asio для того, чтобы использовать ее с сопрограммами c 20. Такая странная необходимость возникла из-за невозможности установить тип поля conn, которое должно быть возвращено.

Тип, который мне нужен, выводится при вызове оператора () для объекта ozo :: request_op <Initiator> reqOp; .Обратный вызов передается оператору (), где вторым аргументом является переменная нужного мне типа. Тип, определенный для второго аргумента обратного вызова, должен быть определен для ожидающего поля класса conn.

В данном случае я определил тип соединения ConnectionPtr, но это неверно, потому что оно подходит только для одного конкретного случая. Мне нужно сделать этот функтор более универсальным. Существует также вариант передачи conn из await_suspend в await_resume, но, насколько я знаю, это можно сделать только через класс awaiter.

  using ConnectionPtr = std::shared_ptr<ozo::pooled_connection<
            yamail::resource_pool::handle<ozo::connection_rep<ozo::oid_map_t<>,
                    ozo::none_t>>, boost::asio::io_context::executor_type>>;

    template<typename Initiator>
    class RequestOp {
    private:
        ozo::request_op<Initiator> reqOp;
    private:

        template<typename P, typename Q, typename TimeConstraint, typename Out>
        struct Awaiter {
            const RequestOp<Initiator> amp;reqOp;
            P amp;amp;provider;
            Q amp;amp;query;
            TimeConstraint t;
            Out out;

            ozo::error_code ec;
            ConnectionPtr conn;

            bool await_ready() {
                return false;
            }

            void await_suspend(std::coroutine_handle<> coro) {
                reqOp.reqOp(std::move(provider), std::move(query), t, out, [this, coro](ozo::error_code ec,
                                                                                        auto conn) mutable {
                    this->ec = ec;
                    this->conn = conn;
                    coro.resume();
                });

            }

            decltype(auto) await_resume() {
                return std::pair<ConnectionPtr, ozo::error_code>{conn, ec};
            }
        };

    public:
        template<typename P, typename Q, typename TimeConstraint, typename Out>
        decltype(auto) operator()(P amp;amp;provider, Q amp;amp;query, TimeConstraint t, Out out) const {
            return Awaiter<P, Q, TimeConstraint, Out>{*this, std::forward<P>(provider), std::forward<Q>(query), t, out};
        }
    };

    constexpr RequestOp<ozo::detail::initiate_async_request> Request;
 

Я понимаю, что все типы в c определяются во время компиляции. Тип, передаваемый обратному вызову, также определяется во время компиляции. Проблема в том, что я не знаю, как сообщить компилятору, какой тип следует определить для поля conn. Хотя во время компиляции тип, который мне нужен, уже известен, он определен для второго аргумента функции обратного вызова, переданной в запрос operator ()

Конечная цель состоит в том, чтобы передать conn в await_resume и вернуться из него, но, к сожалению, await_resume не принимает аргументов, и это должно быть сделано через класс awaiter. Может быть, есть другие способы?

Комментарии:

1. Примечание: decltype(auto) обычно в этом нет необходимости, чистый auto так же хорош. decltype(auto) это один из случаев представления, когда круглые скобки вокруг возвращаемых значений являются релевантными и могут приводить к висячим ссылкам.

2. «Тип поля класса» всегда определяется во время компиляции, и вы уже должны это знать. Во время выполнения в этом никогда не бывает никакой тайны. Это единственный способ работы C . Если вы все еще считаете, что вам нужно «определить» тип какого-либо поля, вам необходимо предоставить дополнительную информацию о том, что вам неясно, или перефразировать свой вопрос, потому что, по крайней мере, ответ будет «вы уже знаете это, иначе ваша программа на C не будет компилироваться».

3. Я понимаю, что все типы в c определяются во время компиляции. Тип, передаваемый обратному вызову, также определяется во время компиляции. Проблема в том, что я не знаю, как сообщить компилятору, какой тип следует определить для поля conn. Хотя во время компиляции нужный мне тип уже известен, он определен для второго аргумента функции обратного вызова, переданной в запрос operator (). @Самваршавчик

4. Тип «второго аргумента функции обратного вызова», по-видимому, таков Q . Это то, чем это объявлено. Если параметр функции обратного вызова сам по себе является функцией, и вы хотите знать тип ее аргумента, то это простая задача для помощника по специализации, но вы должны быть в состоянии объяснить эту проблему гораздо более простым способом, используя гораздо более простой пример, без привлечения специализированной сторонней библиотеки, но используя только стандартные, собственные типы C .

5. decltype(conn) я бы отдал его тебе. Возможно std::remove_cvref_t<decltype(conn)> , А.

Ответ №1:

В общем случае вы не можете этого сделать, потому что тип параметра conn определяется в другом месте, кодом, который в некоторых случаях может быть объявлен после определения этого класса. Если функция, принимающая обратный вызов , возвращает что-то, тип которого основан на типе, возвращаемом обратным вызовом, вы можете написать что-то вроде decltype(reqOp.reqOp(…,[](auto x) {return x;})) , но в противном случае информация о том, какой тип был передан, теряется. (Существуют приемы метапрограммирования с отслеживанием состояния, которые можно использовать для фильтрации информации с помощью аналогичного трюка, но их лучше избегать.)