Стоило отвлечься на пару недель на написание документации и статей, как остаточные знания C++ улетучились из головы и случились жуткие тормоза при попытке написать условие для SFINAE :(
Нужно было вот что: в шаблонный фабричный метод передается ссылка на контейнер (или что-то, что притворяется контейнером, какой-нибудь range, к примеру). Нужно, чтобы этот шаблонный фабричный метод попадал в рассмотрение компилятора только если передали ссылку на контейнер/range. Для этого я решил написать проверку того, что у контейнера есть методы begin/end, и что при разыменовании итератора, возвращенного begin-ом, получается нужный мне тип.
Затык случился при попытке написать проверку на наличие begin/end. Вроде как в C++17 нет штатных средств проверить, что decltype(std::declval<const Another_Container &>().begin()) является валидным типом. Т.е. не нашел в type_traits чего-то вроде is_valid_v<constexpr-expr>. Пришлось извращаться вот таким образом:
template< typename Another_Container > static std::enable_if_t< !std::is_same_v< Container, Another_Container > && !std::is_same_v< decltype(std::declval<const Another_Container &>().begin()), void > && !std::is_same_v< decltype(std::declval<const Another_Container &>().end()), void > && std::is_same_v< std::decay_t< decltype(*(std::declval<const Another_Container &>().begin())) >, mbox_t >, mbox_t > make( environment_t & env, const Another_Container & destinations ) { return make( env, destinations.begin(), destinations.end() ); } |
Т.е. сравнивать тип итератора, возвращаемого begin/end, с void. Ведь итератор, в принципе, не может иметь тип void. Поэтому если begin/end есть, то возвращать они должны не void.
Написать-то написал. И вроде как оно работает. Но не оставляет ощущение, что сильно туплю. И что можно как-то проще. Но вот как непонятно (особенно без введения собственных вспомогательных типов, аналогичных гипотетическому is_valid_v).
Upd. Обновленный текущий вариант, полученный в результате обсуждения в FB, пока выглядит так:
template< typename Another_Container > static std::enable_if_t< !std::is_same_v< Container, Another_Container > && std::is_convertible_v< decltype( ++std::declval< std::add_lvalue_reference_t< decltype(std::begin(std::declval<const Another_Container &>()))> >() == std::end(std::declval<const Another_Container &>()) ), bool> && std::is_same_v< std::decay_t< decltype(*std::begin(std::declval<const Another_Container &>())) >, mbox_t >, mbox_t > make( |
Здесь сделано упрощение + поддержка C-шных массивов + несколько дополнительных проверок, которых не было ранее, а должны были бы быть. И, что самое интересное, это даже VC++ компилируется (как VS2019, так и VS2017).
Upd.2 У варианта с SFINAE обнаружилось два серьезных недостатка. Первый, который был виден изначально, это отсутствие поддержки ADL. Т.е. если Another_Container -- это какой-то пользовательский тип, для которого пользователем определены собственные begin/end функции, то SFINAE бы его забраковал. Поскольку внутри SFINAE используются std::begin/std::end. И как внутри условия SFINAE разбираться с ADL без создания каких-то дополнительных вспомогательных сущностей -- это отдельный квест.
Второй недостаток -- это неспособность Doxygen-а сгенерировать документацию для метода с навороченными условиями SFINAE. Показанный в первом апдейте вариант кода Doxygen не может переворить и просто не включает такой make в итоговую документацию :(
Посему в итоге отказался от SFINAE, а все проверки перенес внутрь метода в static_assert-ы. Получилось что-то вроде:
template< typename Another_Container > static mbox_t make( environment_t & env, const Another_Container & destinations ) { using std::begin; using std::end; // Ensure that destinations if a container or range-like object // with mbox_t inside. static_assert( std::is_convertible_v< decltype( ++std::declval< std::add_lvalue_reference_t< decltype(begin(std::declval<const Another_Container &>()))> >() == end(std::declval<const Another_Container &>()) ), bool>, "destinations should be a container or range-like object" ); static_assert( std::is_same_v< std::decay_t< decltype(*begin(std::declval<const Another_Container &>())) >, mbox_t >, "mbox_t should be accessible via iterator for destinations container (or " "range-like object" ); return make( env, begin(destinations), end(destinations) ); } |
PS. Ну и современный C++ в очередной раз оставил впечатление вроде "ну круто, чё, а нельзя ли все тоже самое, но раза в полтора-два попроще?". Так что ждем C++20 с концептами.
3 комментария:
А почему detection idiom и `std::void_t` из C++17 не подходит? На cppreference пример как раз для begin/end : void_t
@Pavel
Если бы нужно было проверить только наличие begin/end, то void_t подошел бы. Но мне нужны и другие проверки, которые выдают bool.
Это все потому что west const, был бы east const - все намного легче бы прлучилось...
Отправить комментарий