Dado o seguinte código C++23 ( Godbolt ):
template<class R, class T>
concept container_compatible_range =
std::ranges::input_range<R> && std::convertible_to<std::ranges::range_reference_t<R>, T>;
template<class K, class V>
struct Map {
using value_type = std::pair<K, V>;
template<class R>
requires container_compatible_range<R, value_type>
void insert_range_bad(R&& rg) {
for (value_type e : rg) {
// ~~~~
}
}
template<class R>
requires container_compatible_range<R, value_type>
void insert_range_good(R&& rg) {
std::ranges::for_each(rg, [&](value_type e) {
// ~~~~
});
}
};
int main() {
Map<int, int> m;
std::tuple<int, int> a[2] = {}; // for example
m.insert_range_bad(a);
m.insert_range_good(a);
}
Disseram-me que há "casos extremos artificiais" em que insert_range_good
o programa compila com sucesso, mas insert_range_bad
falha (de uma forma hostil ao SFINAE, com erros graves) e/ou faz a coisa errada.
Quais são esses casos extremos?
Os loops baseados em intervalo
for
extraem o iterador e o sentinela do intervalo de maneira consistente , o que significa que ambos devem ser obtidos por meio de função membro ou funções livres.Este não é o caso de
ranges::for_each
, pois ambos podem ser recuperados de maneiras diferentes viaranges::begin
eranges::end
respectivamente:Outro caso artificial é excluir os membros
begin
/ , mas habilitar funções /end
livres ; a classe ainda satisfaz o conceito, pois / verifica a validade da expressão, enquanto os loops baseados em intervalo não o fazem:begin
end
range
ranges::begin
ranges::end
for