четверг, 11 августа 2022 г.

[prog.c++] Внезапное продолжение вчерашней темы и неожиданное для меня поведение компиляторов GCC и clang

Сегодня ради спортивного интереса попробовал добить начатую вчера тему с созданием хитрого предиката для static_assert. Там нужно было сделать проверку для двух случаев. Для первого случая я сделал быстро, а про второй случай подумал, что он гораздо сложнее. Но, оказалось, что не так уж сложнее. Так что предикат получить удалось.

Но вот поведение компиляторов GCC и clang при его использовании меня удивило. С VC++ не проверял, не знаю как бы он себя повел бы.

Под катом большой кусок кода и короткий рассказ о том, как же вели себя компиляторы.

Вот код. Основная часть старая, дописано было лишь то, что относиться к is_valid_handler_for_extra_data_2. Обратить внимание нужно на место в коде, которое помечено как (1). Этот код нормально воспринимается GCC-11 и clang-14.

namespace path_to_params_details
{

template<
   typename F,
   typename Extra_Data,
   typename Tuple,
   std::size_t... Indexes >
decltype(auto)
call_with_tuple_impl(
   F && what,
   const generic_request_handle_t< Extra_Data > & req,
   Tuple && params,
   std::index_sequence<Indexes...> )
{
   return std::forward<F>(what)(
         req, // (1)
         std::get<Indexes>(std::forward<Tuple>(params))... );
}

//
// call_with_tuple
//
/*!
 * @brief A helper function to call a request-handler with a tuple.
 *
 * This helper passes every item of a tuple as a separate parameter.
 *
 * @since v.0.6.6
 */
templatetypename F, typename Extra_Data, typename Tuple >
decltype(auto)
call_with_tuple(
   F && what,
   const generic_request_handle_t< Extra_Data > & req,
   Tuple && params )
{
   return call_with_tuple_impl(
         std::forward<F>(what),
         req,
         std::forward<Tuple>(params),
         std::make_index_sequence<
               std::tuple_size< std::remove_reference_t<Tuple> >::value
         >{} );
}

/* namespace path_to_params_details */

//FIXME: document this!
template<
   typename Handler,
   typename Extra_Data,
   typename Tuple,
   typename = restinio::utils::metaprogramming::void_t<> >
struct is_valid_handler_for_extra_data_2 : public std::false_type {};

template<
      typename Handler,
      typename Extra_Data,
      typename Tuple >
struct is_valid_handler_for_extra_data_2<
      Handler,
      Extra_Data,
      Tuple,
      restinio::utils::metaprogramming::void_t<
         decltype(
            path_to_params_details::call_with_tuple(
               std::declval<Handler &>(),
               std::declval<const generic_request_handle_t<Extra_Data> &>(),
               std::declval<Tuple &>()
            )
         )
      >
   > : public std::true_type {};

//
// path_to_params_producer_t
//
/*!
 * @brief An implementation of a producer for path_to_params case.
 *
 * This implementation produces a tuple and provides a way to call a
 * request-handler with passing that tuple as a set of separate
 * parameters.
 *
 * @since v.0.6.6
 */
template<
   typename Target_Type,
   typename Subitems_Tuple >
class path_to_params_producer_t
   :  public ep::impl::produce_t< Target_Type, Subitems_Tuple >
{
   using base_type_t = ep::impl::produce_t< Target_Type, Subitems_Tuple >;

public:
   using base_type_t::base_type_t;

   templatetypename Handler, typename Extra_Data >
   using handler_checker_metafunc = is_valid_handler_for_extra_data_2<
         Handler,
         Extra_Data,
         typename base_type_t::result_type >;

   templatetypename User_Type, typename Handler >
   RESTINIO_NODISCARD
   static auto
   invoke_handler(
      const generic_request_handle_t< User_Type > & req,
      Handler && handler,
      typename base_type_t::result_type & type )
   {
      return path_to_params_details::call_with_tuple( handler, req, type );
   }
};

В таком виде static_assert не срабатывал под GCC-9.4. Для GCC-9.4 пришлось в is_valid_handler_for_extra_data_2 писать вот так:

template<
      typename Handler,
      typename Extra_Data,
      typename Tuple >
struct is_valid_handler_for_extra_data_2<
      Handler,
      Extra_Data,
      Tuple,
      restinio::utils::metaprogramming::void_t<
         decltype(
            path_to_params_details::call_with_tuple_impl(
               std::declval<Handler &>(),
               std::declval<const generic_request_handle_t<Extra_Data> &>(),
               std::declval<Tuple &>(),
               std::make_index_sequence< std::tuple_size< Tuple >::value >{}
            )
         )
      >
   > : public std::true_type {};

Как бы то ни было, суть в том, что когда Handler оказывается недопустимого формата, то GCC и Clang выдают следующие сообщения об ошибках:

  1. О том, что в точке (1) у параметра req оказывается недопустимый тип;
  2. О сработавшем static_assert-е (это и ожидалось);
  3. О том, что в точке (1) у параметра req оказывается недопустимый тип (это и ожидалось, т.к. компилятор в C++ не останавливается на первой ошибке, а пытается найти еще больше ошибок);

Неожиданным для меня оказалось то, что компилятор выдал сообщение об ошибке, которое он диагностировал разворачивая предикат для static_assert-а. Т.е. ошибка была диагностирована, по сути, в рамках процесса SFINAE, и об этой ошибке компилятор мне сообщил.

Хотя я ожидал, что ошибки, на которые компилятор натыкается в процессе перебора неудачных вариантов SFINAE, компилятором "проглатываются".

А в моем случае эта ошибка не была "проглочена". Поэтому-то компилятор выдал мне ее дважды. Чему я был сильно удивлен. Мягко говоря ;)

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