Tenho experimentado std::ranges::views
e notado que cada visualização encadeada fica maior. Para iteradores de visualização, esse padrão é simples: cada um é sempre 8 bytes maior que o anterior. Mas para as visualizações em si, o transform
tamanho não aumenta em nada, mas filter
aumenta em uma quantidade que não consigo entender. No MSVC, esse padrão é um pouco diferente, mas ainda imprevisível para mim.
Código de teste:
#include <iostream>
#include <ranges>
#include <string>
int main() {
static constexpr auto printed = [](auto&& v) {
std::cout << "sizeof(v): " << sizeof(v) << ", ";
std::cout << "sizeof(it): " << sizeof(v.begin()) << ", ";
std::cout << "values: { ";
for (auto&& x : v) {
std::cout << x << ", ";
}
std::cout << "}\n";
return v;
};
using namespace std::ranges::views;
auto hundred = printed(iota(0ull, 100ull));
auto small = printed(hundred | filter([](auto i) { return i < 50; }));
auto times_seven = printed(small | transform([](auto i) { return i * 7; }));
auto even = printed(times_seven | filter([](auto i) { return i % 2 == 0; }));
auto as_string = printed(even | transform([](auto i) { return std::to_string(i); }));
auto no_twos = printed(as_string | filter([](auto s) { return s.find('2') == s.npos; }));
}
Resultados:
visualizar | tamanho(dele) | delta | sizeof(visualização), GCC/Clang | delta | sizeof(visualização), MSVC | delta |
---|---|---|---|---|---|---|
iota | 8 | 16 | 16 | |||
filtro | 16 | 8 | 32 | 16 | 48 | 32 |
transformar | 24 | 8 | 32 | 0 | 64 | 16 |
filtro | 32 | 8 | 64 | 32 | 112 | 48 |
transformar | 40 | 8 | 64 | 0 | 128 | 16 |
filtro | 48 | 8 | 112 | 48 | 192 | 64 |
Meu código não se importa muito com o tamanho das visualizações, já que elas nunca são armazenadas em lugar nenhum. Só estou me perguntando por que elas não podem ser constantes, dependendo apenas do tamanho do lambda (aqui sempre vazio). Tentei analisar o código-fonte, mas acho muito difícil de ler com todas as camadas de classes base, especialização de modelos e restrições de conceitos.
Para manter o tempo constante amortizado
ranges::begin
garantido pelorange
conceito,filter_view
é necessário armazenar em cache o primeiro iterador da visualização subjacente que corresponde ao predicado, para que cada chamada ao seubegin()
possa retornar o iterador armazenado em cache sem buscas repetidas.É por isso que
filter_view
's nãobegin()
é qualificado como - , porque o primeiro iterador precisa ser armazenado em cache e em si mesmo quando é chamado pela primeira vez.const
filter_view
begin()
Isso significa que, diferentemente de
transform_view
, aofilter_view
atuar em outras visualizações, além de salvar cópias da visualização subjacente e dos predicados, ele também precisa salvar seu iterador.O valor aumentado é o tamanho do cache interno que salva o iterador da visualização subjacente.
O objeto de função é armazenado em
filter_view
sitransform_view
mesmo, então seus iteradores precisam salvar não apenas o iterador subjacente, mas também um ponteiro extra (8 bytes) para a classe de visualização para facilitar o acesso ao objeto de função.É por isso que esses dois nunca são
borrowed_range
, porque quando eles são destruídos, seus iteradores acessarão ponteiros pendentes.