C ++ 11 é ótimo. Provavelmente um dos recursos mais bonitos (na minha opinião) é o chamado loop baseado em intervalo. Em vez de
for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; }
ou
for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
ou simplificado com C ++ 11 auto:
for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
podemos dizer o seguinte:
for ( auto& entry : range ) { // do something with entry. }
Isso é muito expressivo: nós conversamos sobre a entrada, em vez da i ^ ésima posição no intervalo ou do iterador apontando para uma entrada. No entanto, essa sintaxe não tem a capacidade de ter subintervalos (por exemplo, ignorar a última entrada). Em uma série de pequenas estruturas / métodos auxiliares, quero adicionar essa funcionalidade de uma maneira limpa.
Isso é para minha motivação 😉 Agora, para o negócio real.
Nesta postagem, abordo as condições nas entradas de range
. Basicamente, o código auxiliar deve executar o equivalente a
for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } }
, mas sem esse nível de indentação.
Aqui está o código para ConditionalRange:
template <typename Range, typename Runnable> ConditionalRange<Range, Runnable> makeConditionalRange(Range& range, Runnable&& condition) { static_assert(std::is_same<decltype(condition(*std::declval<Range>().begin())), bool>::value, "Condition must return a boolean value."); return ConditionalRange<Range, Runnable>(range, std::forward<Runnable>(condition)); } template <typename Range, typename Runnable> struct ConditionalRange { public: friend ConditionalRange makeConditionalRange<>(Range&, Runnable&&); public: using iterator_type = ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>; iterator_type begin() const { auto b = range.begin(); while ( b != range.end() && !condition(*b) ) { ++b; } return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(b, range.end(), condition); } iterator_type end() const { return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(range.end(), range.end(), condition); } private: ConditionalRange(Range& range_, Runnable&& condition_) : range(range_), condition(std::forward<Runnable>(condition_)) { } private: Range& range; Runnable condition; };
Esta é a estrutura auxiliar para o iterador:
template <typename Iterator, typename Runnable> struct ConditionalIterator { private: Iterator iterator; Iterator end; const Runnable& condition; public: ConditionalIterator(Iterator b, Iterator e, const Runnable& r) : iterator(b), end(e), condition(r) { } auto operator*() -> decltype(*iterator) { return *iterator; } ConditionalIterator& operator++() { do { ++iterator; } while ( iterator != end && !condition(*iterator) ); return *this; } bool operator==(const ConditionalIterator& second) { return iterator == second.iterator; } bool operator!=(const ConditionalIterator& second) { return !(*this == second); } };
O uso pretendido é:
std::vector<int> ns{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for ( const auto& n : makeConditionalRange(ns, [](int a) { return a % 2 == 0; }) ) { std::cout << n << " "; } std::cout << "\n";
Demo: clique aqui!
O que são os pontos fracos da minha abordagem? Com que tipo de argumentos ele falha, embora não devesse no mundo perfeito?
Comentários
Resposta
Você pode usar colchetes para retornar de suas funções. Dessa forma, você “não terá que repetir tipos longos e complicados (DRY, tanto quanto possível). Por exemplo, ConditionalRange::end
:
iterator_type end() const { return { range.end(), range.end(), condition }; }
Além disso, você provavelmente deseja que sua função também aceite matrizes de estilo C. Portanto, você deve usar std::begin
e std::end
em vez de chamar as funções de membro:
iterator_type end() const { return { std::end(range), std::end(range), condition }; }
Com isso (em todos os lugares em seu código) e algumas mudanças triviais, seu código também funcionará para C- arrays de estilo (testado aqui e funciona bem).
Nomeando sua função
O nome makeConditionalRange
é bastante longo. A função que você criou já existe em outras bibliotecas e linguagens de programação (Python vem à minha mente) com o nome filter
. mudar seu nome para que muitos usuários reconheçam o nome à primeira vista (além disso, será mais curto).
for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
filter_iterator
?operator!=
otimiza isso emoperator==