Я люблю С++ за то, что код делающий что-то простое может быть сколь угодно простым, но в то-же время сколь угодно сложным.
Например нужно написать функцию добавляющую элемент в контейнер, но конкретный тип контейнера неизветсен, вот простой вариант:
#include <iostream>
#include <vector>
template <class C, class T>
void add (T inserter, C value)
{
*inserter = value;
}
int main(int argc, char *argv[])
{
std::vector <int> arr;
add(std::inserter(arr, arr.begin()), 1);
add(std::inserter(arr, arr.begin()), 2);
add(std::inserter(arr, arr.begin()), 3);
for (int i = 0; i != arr.size(); i++)
{
std::cout << arr[i] << std::endl;
}
system("pause");
}
Функция add получает 2 параметра, первый должен быть insert_iterator-ом, второй - значение которое добавляется в контейнер. Что здесь не так?
Тип первого параметра может быть любым, можно например засунуть туда указатель на int и все скомпилируется, но будет неправильно работать.
По сигнатуре функции непонятно что она должна получать, указатель на int, или все-же insert_iterator (это не очень понятно и по коду функции (:
Если объяснить компилятору более доходчиво, то получится что-то вроде этого:
#include <iostream>
#include <vector>
#include <list>
template <
template <class T, class A> class container,
template <class T> class allocator,
class Arg
>
void add ( std::insert_iterator< container<Arg, allocator<Arg> > > inserter, Arg value )
{
*inserter = value;
}
int main(int argc, char *argv[])
{
std::vector <int> arr;
add(std::inserter(arr, arr.begin()), 1);
add(std::inserter(arr, arr.begin()), 2);
add(std::inserter(arr, arr.begin()), 3);
for (int i = 0; i != arr.size(); i++)
{
std::cout << arr[i] << std::endl;
}
system("pause");
}
Этот код немного сложнее чем предыдущий. Функция add работает так-же, но имеет теперь три шаблонных параметра а не два, причем первые два - шаблонные(шаблонные параметры) . И теперь указатель на int туда передать нельзя, что очень хорошо.
Уверен что можно усложнить этот пример еще и даже получить из этого какую-то пользу, возможности С++ безграничны :))
Update
В этом коде есть небольшая ошибка, в функцию add, insert_iterator нужно передавать по ссылке, если передавать по значению, то будут проблемы с вектором. Дело в том, что после перераспределения памяти, итератор, который передается в add не изменится, а изменится только его локальная копия. Это приведет к тому, что итератор, указывающий на начало вектора, который хранится внутри insert_iterator-a станет показывать в неизвестность (пример работает потому, что inserter создается для каждого вызова add заново). Для list и deque это неактуально, так как там insert не приводит к порче итераторов.
По моему вы пошли по неправильному пути: надо было не защищать функцию от передачи указателя, а наоборот поддержать такое поведение, написав
ОтветитьУдалить*inserter++ = value;
Тогда в функцию можно будет передавать любой output iterator.
Вместо этого вы специализировали функцию только для работы с std::inserter. Так в C++ не делается =). А если мне потом придется обернуть этот инсертер, чтобы он округлял значения до тысячи? Если уж было необходимо было защититься от передачи в функцию указателя, это можно было сделать это явно при помощи static-assert или SFINAE.
Далее. Ваш коментарий по поводу протухания итераторов вектора после переаллокации не лишен здавого смысла, но std::inserter (как и прочие std::*_inserter-ы) должен это обрабатывать (он хранит ссылку на контейнер), т.к. польностью поддерживает интерфейс output iterator, а значит может передаваться по значению.
В общем вы правы, код получился не гибкий. Но, просто этот надуманый пример, должен был продемонстрировать, как можно наложить ограничения на параметр шаблона, вот так..)
ОтветитьУдалитьпо поводу протухания итераторов, я то-же так думал, но все-же его нельзя передавать по значению..
опреатор присваивания для insert_iterator-a реализован примерно так:
iter = container.insert(iter, value);
iter++;
return *this;
тоесть итератор, внутри конкретного insert_iterator-a останется валидным в любом случае.
Возможно проблема в том, что iter - передается по значению при копировании, поэтому, когда iter изменяется внутри функции, исходный итератор об этом не знает...
в общем, проще немного переделать мой пример:
std::insert_iterator iter = std::inserter(arr, arr.begin());
for(int i = 0; i < 100; ++i)
{
add(iter, i);
}
и попробовать передавать итератор в add по ссылке и по значению)).
В общем вы правы, код получился не гибкий. Но, просто этот надуманый пример, должен был продемонстрировать, как можно наложить ограничения на параметр шаблона, вот так..)
ОтветитьУдалитьпо поводу протухания итераторов, я то-же так думал, но все-же его нельзя передавать по значению..
опреатор присваивания для insert_iterator-a реализован примерно так:
iter = container.insert(iter, value);
iter++;
return *this;
тоесть итератор, внутри конкретного insert_iterator-a останется валидным в любом случае.
Возможно проблема в том, что iter - передается по значению при копировании, поэтому, когда iter изменяется внутри функции, исходный итератор об этом не знает...
в общем, проще немного переделать мой пример:
std::insert_iterator iter = std::inserter(arr, arr.begin());
for(int i = 0; i < 100; ++i)
{
add(iter, i);
}
и попробовать передавать итератор в add по ссылке и по значению)).