Сегодня ради спортивного интереса попробовал добить начатую вчера тему с созданием хитрого предиката для 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 */ template< typename 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; template< typename Handler, typename Extra_Data > using handler_checker_metafunc = is_valid_handler_for_extra_data_2< Handler, Extra_Data, typename base_type_t::result_type >; template< typename 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) у параметра req оказывается недопустимый тип;
- О сработавшем static_assert-е (это и ожидалось);
- О том, что в точке (1) у параметра req оказывается недопустимый тип (это и ожидалось, т.к. компилятор в C++ не останавливается на первой ошибке, а пытается найти еще больше ошибок);
Неожиданным для меня оказалось то, что компилятор выдал сообщение об ошибке, которое он диагностировал разворачивая предикат для static_assert-а. Т.е. ошибка была диагностирована, по сути, в рамках процесса SFINAE, и об этой ошибке компилятор мне сообщил.
Хотя я ожидал, что ошибки, на которые компилятор натыкается в процессе перебора неудачных вариантов SFINAE, компилятором "проглатываются".
А в моем случае эта ошибка не была "проглочена". Поэтому-то компилятор выдал мне ее дважды. Чему я был сильно удивлен. Мягко говоря ;)
Комментариев нет:
Отправить комментарий