На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Модераторы: Qraizer, Hsilgos
  
> noexcept в параметре шаблона типа указателя на функцию
    Здравствуйте!

    С удивлением обнаружил, что noexcept влияет на тип шаблона.

    Имеется класс:
    ExpandedWrap disabled
      template< typename CallableType >
      class callable_wrapper;
       
      template< typename ResultType, typename ...ArgsType >
      class callable_wrapper< ResultType( * )( ArgsType... ) >
      {
      public:
              using result_type = ResultType;
      };


    Шаблон класса callable_wrapper, для обёртки указателя на функцию и вызова этой функции. (Здесь я порезал код вызова.( Он у меня используется в качестве базового класса, если кому-то интересно.

    Объект создаётся явно, нечто вроде:
    ExpandedWrap disabled
      callable_wrapper< decltype( &funcname ) >( &funcname );


    Внезапно, выяснил, что этот шаблон класса, принимает указатели на функции без noexcept. А если передать noexcept, то оно не компилируется.

    Поправил шаблон:
    ExpandedWrap disabled
      template< typename ResultType, typename ...ArgsType >
      class callable_wrapper< ResultType( * )( ArgsType... ) noexcept >
      {
      public:
              using result_type = ResultType;
      };

    И получилось наоборот. Принимает только noexcept, а без noexcept уже ошибка.

    Попытался вывести параметр:
    ExpandedWrap disabled
      template< typename ResultType, typename ...ArgsType, bool IsNoexcept  >
      class callable_wrapper< ResultType( * )( ArgsType... ) noexcept( IsNoexcept ) >


    Но в таком варианте не компилируется, невозможно вывести параметр шаблона.

    Возникло несколько вопросов:
    1. Придётся делать вторую специализацию шаблона, отдельно для noexcept и отдельно без noexcept? Или лучше совместить? Меня бы устроило приведение всех функций к noexcept.
    2. А какие атрибуты ещё влияют на тип указателя функции, также как noexcept?
    3. Есть ли возможность определить в compile-time наличие атрибутов noreturn, nodiscard?
    4. Что происходит с модификаторами __stdcall, __cdecl и другими, при передаче указателя в тип шаблона? Они же изменяют тип. Но почему-то шаблоны не специализируются.
    Сообщение отредактировано: Eric-S -
      Интересный вопрос! Но ответа, увы, нет. Подписываюсь на уведомления :good:

      Добавлено
      Цитата Eric-S @
      принимает указатели на функции без noexcept

      Как вариант для попытки решения ... может как-то впилить в код обертку в виде std::function? Может это как-то поможет...
        [QUOTE=Majestio,1654072481,3867072]принимает указатели на функции без noexcept[/quote]
        Как вариант для попытки решения ... может как-то впилить в код обертку в виде std::function? Может это как-то поможет...[/QUOTE]
        Хотелось бы получить вариант лёгкого кода, без излишеств, самый минимум, только хардкор. Увы, std::function его заметно утяжеляет.

        Пока решил сделать две специализации, для noexcept( false ) и noexcept( true ), добавив шаблону параметр bool IsNoexcept.

        А этот самый параметр устанавливать явно, вызвав noexcept( funcname( args... ) ). Решение грубое, но в теории должно сработать.
          Цитата Eric-S @
          Пока решил сделать две специализации, для noexcept( false ) и noexcept( true ), добавив шаблону параметр bool IsNoexcept.

          Ну да, так конечно легче будет. Согласен.
            Поисследовал проблему. Начиная с C++17 спецификация noexcept является частью типа, поэтому функции (и методы с операторами, естественно), отличающиеся лишь этим спецификатором, частично совместимы друг с другом. Т.е., например, указателю на noexcept(true) можно присвоить noexcept(false), но не наоборот. Однако при сопоставлении типов совместимость не применяется и требуется точное соответствие. Формально реализации способны вывести значение noexcept(), т.к. для каждого типа это представляет собой значение периода компиляции, однако Стандарт не содержит требований для этого. В 18-м году было внесено предложение считать это дефектом и открыто голосование. Буквально 7 мая сего года оно было одобрено.
              Цитата Qraizer @
              однако Стандарт не содержит требований для этого. В 18-м году было внесено предложение считать это дефектом и открыто голосование. Буквально 7 мая сего года оно было одобрено.

              О как! Спасибо! Видимо я как раз наткнулся. Компилятор ведёт себя странно. Параметр не выводит. Путается в типах.
                Шаманил. И так и сяк крутил. Не хотел компилятор различать noexcept в шаблонах.

                В итоге сляпал грубый костыль для получения типа указателя на функцию, без модификатора noexcept.

                Шаблон remove_noexcept, первым параметром тип указателя на функцию, а вторым параметром флаг есть ли noexcept. Увы, у меня автоматически не определяет наличие noexcept, только ругается.

                ExpandedWrap disabled
                  template< typename CallableType, bool IsNoexcept >
                  struct remove_noexcept;
                   
                  template< typename ResultType, typename ...ArgsType >
                  struct remove_noexcept< ResultType( * )( ArgsType... ), false >
                      {
                      using type = typename ResultType( * )( ArgsType... );
                      };
                   
                  template< typename ResultType, typename ...ArgsType >
                  struct remove_noexcept< ResultType( * )( ArgsType... ) noexcept, true >
                      {
                      using type = typename ResultType( * )( ArgsType... );
                      };
                   
                  template< typename ResultType, typename ...ArgsType >
                  struct remove_noexcept< ResultType( & )( ArgsType... ), false >
                      {
                      using type = typename ResultType( * )( ArgsType... );
                      };
                   
                  template< typename ResultType, typename ...ArgsType >
                  struct remove_noexcept< ResultType( & )( ArgsType... ) noexcept, true >
                      {
                      using type = typename ResultType( * )( ArgsType... );
                      };
                   
                  template< typename ResultType, typename ...ArgsType >
                  struct remove_noexcept< ResultType( *& )( ArgsType... ), false >
                      {
                      using type = typename ResultType( * )( ArgsType... );
                      };
                   
                  template< typename ResultType, typename ...ArgsType >
                  struct remove_noexcept< ResultType( *& )( ArgsType... ) noexcept, true >
                      {
                      using type = typename ResultType( * )( ArgsType... );
                      };


                Заодно приводит ссылку на функцию к указателю на функцию. Идиологически неверно, надо бы разделить.

                Ещё можно сделать макрос, чтоб оно хотя бы выглядело пристойно:
                ExpandedWrap disabled
                  #define  REMOVE_NOEXCEPT( FUNCNAME, ... ) remove_noexcept< decltype( FUNCNAME ), noexcept( FUNCNAME( __VA_ARGS__ ) ) >::type


                Первый параметр указатель на функцию. Последующие параметры, это параметры передаваемые в функцию. А возвращает тип указателя на функцию.

                Ну или как-то так. Мой код теперь компилируется. А про рефакторинг подумаю завтра.
                  А если проще?
                  ExpandedWrap disabled
                    typedef         char            small_type;
                    typedef struct {char dummy[2];} big_type;
                     
                    template <typename ResultType, typename ...ArgsType> small_type check_noexcept(ResultType (*)(ArgsType...));
                    template <typename ResultType, typename ...ArgsType> big_type   check_noexcept(ResultType (*)(ArgsType...) noexcept);
                     
                    /* проверяем */
                    void may_throw();
                    void no_throw() noexcept;
                     
                    constexpr bool throw_true = sizeof(check_noexcept(may_throw)) == sizeof(small_type);
                    constexpr bool throw_false= sizeof(check_noexcept(no_throw))  == sizeof(small_type);


                  Добавлено
                  Эх, были времена, когда в Стандарте не было никакого метапрограммирования. Всё руками, всё руками...
                  Сообщение отредактировано: Qraizer -
                    Цитата Qraizer @
                    А если проще?

                    Очень интересное и своеобразное решение. Наверное, его можно даже упростить, используя inline constexpr функции, возвращающие bool. Спасибо. Подумаю и попробую.

                    Добавлено
                    Цитата Qraizer @
                    Эх, были времена, когда в Стандарте не было никакого метапрограммирования. Всё руками, всё руками...

                    Так лениво же руками повторять одно и тоже. А вот что-нибудь этакое, да ещё само выполняющее работу... Так что автоматизация и ещё раз автоматизация.
                      Цитата Eric-S @
                      Очень интересное и своеобразное решение. Наверное, его можно даже упростить, используя inline constexpr функции, возвращающие bool. Спасибо. Подумаю и попробую.
                      Во времена C++03 принцип SFINAE руками эксплуатировался настолько часто, что даже сейчас у меня руки этот код написали, почитай, сами, голова в этом не участвовала. Нынче этот SFINAE является основной движущей силой в <type_traits> та и не только там. Ну коли гора к Магомеду задом... мы не гордые, повернём Магомеда передом к горе сами. Конечно лучше это инкапсулировать куда-нибудь.

                      Добавлено
                      P.S. Формально этот метакод исполняет алгоритм «Если параметр одной из перегрузок check_noexcept() не подходит, выбери другую перегрузку и скажи, какую выбрал; если не выбрал ничего, остановись с ошибкой». Обе перегрузки имеют параметрами указатели на функции, поэтому любые, что не такие, отклонят обоих кандидатов. Те, что подходят, взаимоисключаются спецификатором, поэтому не могут быть выбраны одновременно, так что у компилятора останется ровно один кандидат. Какой именно, определяется возвращаемым значением, которые спецом выбраны так, что имеют гарантированно разные размеры. Плюс эксплуатируется тот факт, что sizeof() всегда исполняется на стадии компиляции.
                        Цитата Qraizer @
                        Нынче этот SFINAE является основной движущей силой в <type_traits> та и не только там. Ну коли гора к Магомеду задом... мы не гордые, повернём Магомеда передом к горе сами. Конечно лучше это инкапсулировать куда-нибудь.

                        Формально этот метакод исполняет алгоритм «Если параметр одной из перегрузок check_noexcept() не подходит, выбери другую перегрузку и скажи, какую выбрал;


                        Механизм понятен. Но по-моему в нём участвуют лишние сущности. Взял идею, переработал и получил следующий рабочий код:
                        ExpandedWrap disabled
                          /// @brief получает тип указателя на функцию
                          /// @param CALLABLE вызываемая функция.
                          /// @return  тип указателя на функцию.
                          #define CALLABLE_TYPE( CALLABLE ) \
                          callable_type< decltype( CALLABLE ), decltype( has_noexcept( CALLABLE ) ) >::type
                           
                          /// @brief проверка установленного модификатора noexcept
                          /// @param[in] callable проверяемый указатель на функцию
                          /// @return результат проверки.
                          template< typename ResultType, typename ...ArgsType >
                          [[nodiscard]] inline constexpr auto has_noexcept( [[maybe_unused]] ResultType( *callable )( ArgsType... ) ) noexcept -> false_type
                              {
                              return false_type();
                              }
                           
                          /// @brief проверка установленного модификатора noexcept
                          /// @param[in] callable проверяемый указатель на функцию
                          /// @return результат проверки.
                          template< typename ResultType, typename ...ArgsType >
                          [[nodiscard]] inline constexpr auto has_noexcept( [[maybe_unused]] ResultType( *callable )( ArgsType... ) noexcept ) noexcept -> true_type
                              {
                              return true_type();
                              }
                           
                          /// @brief преобразует  вызываемый тип в тип указателя на функцию
                          /// @tparam CallableType тип вызываемой функции.
                          /// @tparam IsNoexcept флаг установки noexcept.
                          template< typename CallableType, typename IsNoexcept >
                          struct callable_type;
                           
                          /// @brief преобразует  вызываемый тип в тип указателя на функцию
                          /// @tparam ResultType тип возвращаемого результата.
                          /// @tparam ArgsType тип аргументов вызова.
                          template< typename ResultType, typename ...ArgsType >
                          struct callable_type< ResultType( * )( ArgsType... ), false_type >
                              {
                              using type = typename ResultType( * )( ArgsType... );
                              };
                           
                          /// @brief преобразует  вызываемый тип в тип указателя на функцию
                          /// @tparam ResultType тип возвращаемого результата.
                          /// @tparam ArgsType тип аргументов вызова.
                          template< typename ResultType, typename ...ArgsType >
                          struct callable_type< ResultType( * )( ArgsType... ) noexcept, true_type >
                              {
                              using type = typename ResultType( * )( ArgsType... );
                              };
                        Сообщение отредактировано: Eric-S -
                        0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                        0 пользователей:


                        Рейтинг@Mail.ru
                        [ Script execution time: 0,0403 ]   [ 17 queries used ]   [ Generated: 10.12.22, 06:42 GMT ]