Przypisać nieprzypisywalne

  • cpp, pl
  • finished

Algorytmy takie jak std::copy czy std::transform działają w ten sposób, że przyjmują pewien OutputIterator i podmieniają wartość przezeń wskazywaną nową wartością:

template <typename OutputIt, typename InputIt>
OutputIt copy(InputIt from, InputIt to, OutputIt out)
{
    while (from != to)
    {
        *out = *from;
        ++out;
        ++from;
    }
    return out;
}

Co jednak zrobić w sytuacji, gdy nasz typ nie posiada operatora przypisania?

Kiedy to się zdarzy?

Sytuacja taka może się zdarzyć na przykład wtedy gdy jest on domyślnie skasowany (implicitly deleted), czyli np. gdy nasza klasa bądź struktura będzie miała const membera:

struct Foo 
{
    Foo() : i(0) {}
    Foo(int i) : i(i) {}

    const int i;
};

std::vector<Foo> v1{ {1}, {2}, {3} };
std::vector<Foo> v2(v1.size());

// To nie zadziała:
std::copy(v1.begin(), v1.end(), v2.begin());

Foo nie posiada operatora przypisania z oczywistych względów: mógłby on modyfikować stałą i. Jednak posiada konstruktor kopiujący. Jak zatem zmusić std::copy do faktycznego skopiowania obiektu, zamiast przypisania (copy assignment)?

Jak żyć?

Na ratunek przychodzą iteratory pomocnicze typu std::insert_iterator, std::back_insert_operator itp. Ich implementacja dostarcza operator przypisania value_type z danego kontenera do samego iteratora, wewnątrz którego kryje się faktyczna implementacja. Coś w tym stylu:

template <typename Container>
class back_insert_iterator
{
    // ...
    back_insert_iterator&  operator=(const typename Container::value_type& val)
    {
        c.push_back(val);
        return *this;
    }

    back_insert_iterator& operator*() { return *this; }
    back_insert_iterator operator++() { return *this; }
    back_insert_iterator operator++(int) { return *this; }
};

Dzięki temu, oraz dzięki temu, że operatory wyłuskania i inkrementacji zwracają *this, nie zostanie wywołany Foo::operator=(const Foo&), ale back_insert_iterator::operator=(const Foo&). Oznacza to ni mniej, ni więcej, że cel skopiowania danych z jednego kontenera do drugiego został osiągnięty:

std::vector<Foo> v1{ {1}, {2}, {3} };
std::vector<Foo> v2;

// To zadziała:
std::copy(v1.begin(), v1.end(), std::back_inserter(v2));