Tenho uma SequencesRange
classe simples que pode iterar sequências genômicas aleatórias de tamanho fixo.
Quando tento juntar os caracteres de todas as sequências com std::views::join
, funciona bem, mas assim que tento canalizar com std::views::enumerate
, obtenho uma saída de lixo. Posso verificar se o valgrind não está muito feliz e produz alguns temidosConditional jump or move depends on uninitialised value(s)
#include <string>
#include <iostream>
#include <ranges>
class SequencesRange
{
public:
struct iterator
{
using iterator_category = std::forward_iterator_tag;
using value_type = std::string;
using difference_type = long;
using pointer = value_type*;
using reference = value_type const&;
iterator (size_t size, size_t nbItems) : value_(size,' '), nbItems_(nbItems)
{
next();
}
iterator() = default;
iterator(iterator const&) = default;
iterator(iterator && ) = default;
iterator& operator= (iterator const&) = default;
iterator& operator= (iterator &&) = default;
bool operator!= (const iterator& other) const { return nbItems_ != other.nbItems_; }
bool operator== (const iterator& other) const { return nbItems_ == other.nbItems_; }
iterator& operator++ () { next(); ++nbItems_; return *this; }
iterator operator++(int) { iterator tmp = *this; ++(*this); return tmp; }
reference operator* () const { return value_; }
value_type value_;
size_t nbItems_ = 0;
void next()
{
static std::array<char,4> letters = {'A','C','G','T'};
for (auto& c : value_) { c = letters[rand()%letters.size()]; }
}
};
public:
iterator begin() const { return iterator(size, 0); }
iterator end () const { return iterator(size, nbItems); }
size_t nbItems = 0;
size_t size = 0;
};
int main (int argc, char** argv)
{
SequencesRange iterable {1, 10}; // one sequence of length 10
for (auto [i,nt] : iterable | std::views::join | std::views::enumerate)
{
std::cout << nt; // got some garbage output here
}
// Note that the following is ok:
// for (auto nt : iterable | std::views::join) { std::cout << nt; }
}
Parece um gerenciamento ruim do ciclo de vida do objeto e suspeito que haja algo bobo na estrutura do meu iterador, mas atualmente não consigo ver onde.
Coisa estranha: se eu configurar o iterável com sequências de comprimento>=16, não tenho erro algum. Talvez um efeito colateral da otimização de string curta?
ATUALIZAR:
Observe que não tenho problemas com uma versão simplificada da iterator
estrutura usando um sentinela inteiro para a end
instância:
ATUALIZAR:
Se eu forçar o iterador a fornecer cópias (por exemplo, value_type operator* () const { return value_; }
), ele funciona.
Mas não consigo entender por que cópias são necessárias, iterable | std::views::join | std::views::enumerate
embora apenas referências possam ser usadas com iterable | std::views::join
.