C ++ Почему большинство функций STL требуют `begin ()` и `end ()` вместо просто контейнера? [Дубликат]

ECMAScript 6 имеет «генераторы», которые позволяют вам легко программировать в асинхронном стиле.

function* myGenerator() {
    const callback = yield;
    let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
    console.log("response is:", response);

    // examples of other things you can do
    yield setTimeout(callback, 1000);
    console.log("it delayed for 1000ms");
    while (response.statusText === "error") {
        [response] = yield* anotherGenerator();
    }
}

Для запуска вышеуказанного кода вы делаете это:

const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function

Если вам нужно настроить таргетинг на браузеры, которые не поддерживают ES6, вы можете запустить код через Babel или short-compiler для генерации ECMAScript 5.

Обратный вызов ...args завернут в массив и разрушен, когда вы их читаете так что шаблон может справиться с обратными вызовами, которые имеют несколько аргументов. Например, с узлом fs :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);

16
задан leemes 1 October 2014 в 11:33
поделиться

7 ответов

Они вводят неоднозначность для многих алгоритмов. Многие <algorithm> выглядят как

template<class iterator>
void do_something(iterator, iterator);

template<class iterator, class funct>
void do_something(iterator, iterator, funct);

Если вы добавите дополнительные перегрузки

template<class container, class funct>
void do_something(container, funct);

, компилятор будет иметь некоторые проблемы с выяснением того, что означает do_something(x, y). Если x и y имеют одинаковый type, он будет соответствовать как iterator = type, так и container = type, funct = type. *)

C ++ 11 попытался решить эту проблему с помощью " концепций ", которые могли бы распознать разницу между контейнером и итератором. Однако эти «концепции» оказались слишком сложными, чтобы превратить их в стандарт, поэтому и эти перегрузки.

*) компиляторы не согласны с этим, компилятор Comeau утверждает, что он неоднозначен, g ++ 4.5 и MSVC 10 вызывает первую функцию.


После чрезвычайно продолжительного обсуждения комментариев, вот один пример, когда он не работает должным образом - с помощью адаптера контейнера который также может удваиваться как предикат.

#include <iostream>
#include <vector>

template<class iterator>
void test(iterator, iterator)
{
   std::cout << "test iterator\n";
}

template<class iterator, class predicate>
void test(iterator, iterator, predicate)
{
   std::cout << "test iterator, predicate\n";
}

template<class container, class predicate>
void test(const container& cont, predicate compare)
{
   std::cout << "test container, predicate\n";

   test(cont.begin(), cont.end(), compare);
}

template<class container>
class adapter
{
public:
   typedef typename container::iterator   iterator;

   adapter(container* cont) : cont(cont)
   { }

   iterator begin() const
   { return cont->begin(); }

   iterator end() const
   { return cont->end(); }

   bool operator()(const iterator& one, const iterator& two)
   { return *one < *two; }

private:
   container* cont;
};

int main()
{
   std::vector<int>   v;

   adapter<std::vector<int>>   a(&v);

   test(a, a);

}

Выход:

test iterator

http: // ideone.com/wps2tZ

17
ответ дан Community 26 August 2018 в 06:45
поделиться

К сожалению, это гораздо более общая проблема; а именно, что итераторы были разработаны для того, чтобы победить эти дерьмовые API-интерфейсы API и Java-стиль «Поместите алгоритмы как методы каждого отдельного контейнера». Они являются универсальным решением первого поколения, и нет ничего удивительного в том, что при отражении они были не так хороши, как другие возможные общие решения, которые можно было получить после того, как мы потратим на это двадцать лет.

Добавление этих перегрузок контейнеров будет просто помогая группе по крошечной части проблемного пространства; и это может даже ухудшить ситуацию в будущем. Решение - это диапазоны, которые C ++ ищет, чтобы ввести ASAP.

10
ответ дан Bejmax 26 August 2018 в 06:45
поделиться

Следует отметить, что очень легко определить ваши собственные тривиальные обертки для добавления контейнеризованных версий.

Например:

template<typename Container, typename Func>
Func for_each(Container& c, Func f) {
    return std::for_each(c.begin(), c.end(), f);
}

Теперь вы можете сделать простой вызов вы хотите. Нет никакой двусмысленности, потому что ваши обертки не находятся в пространстве имен std. Вы можете определить перегрузки, которые принимают const Container & amp ;. Если вам нужны версии, которые вызывают методы итератора C ++-11 const (например, cbegin ()), я думаю, вам нужно будет назвать оболочку по-разному. Я использую for_each_const.

0
ответ дан divegeek 26 August 2018 в 06:45
поделиться

Существует библиотека операторов диапазона с намерением исправить это. Многословие было разрезано несколько раз.

Ваш пример выглядел бы примерно так:

auto newVector = myVector * doSomething;

Да, doSomething - без круглых скобок.

Знакомая идиома из оболочки (с помощью std-алгоритма):

auto t = vector<int>{3,2,1,4} | sort | unique; 
2
ответ дан Leonid Volnitsky 26 August 2018 в 06:45
поделиться

Чтобы понять, что я думаю, нужно понять философию алгоритмов C ++. Давайте сначала зададим этот вопрос:

Почему алгоритмы C ++ реализованы как свободные функции вместо функций-членов?

Ну, ответ довольно прост: избежать взрывов внедрения. Предположим, у вас есть M контейнеры и N алгоритмы, и если вы реализуете их как члены контейнеров, то будут M*N реализации. В этом подходе есть две (связанные) проблемы:

  • Во-первых, он не использует повторное использование кода.
  • C ++ решает эти проблемы, реализуя их как свободные функции , так что у вас есть только N реализации. Каждый из алгоритмов, работающих на контейнере , принимает пару итераторов, которые определяют диапазон . Если вы хотите перегрузки, которые принимают контейнер, а не пару итераторов, то Стандарт должен обеспечить такие перегрузки для каждого из алгоритмов, и будут 2*N реализации, которые в значительной степени победят ту самую цель, почему C ++ отделил алгоритмы от контейнеры в первую очередь, а половина из этих функций не делает ничего, что не может быть сделано другой половиной.

    Поэтому я не думаю, что это такая проблема. Чтобы избежать одного единственного аргумента, зачем использовать N больше функций (которые также налагают ограничение на его использование, например, вы не можете передать ему указатели )? Однако, если программисты хотят использовать такие функции в своей утилите, они могут реализовать их в любое время вместе со многими другими на основе стандартного алгоритма!


    Вы прокомментировали:

    Well , реализация 2 * N фактически является только N реализациями. Остальные N являются встроенными перегрузками, которые прямо называют «реальную» версию алгоритма, поэтому они являются только заголовками. Предоставление перегрузок контейнеров не отменяет цели разделения алгоритмов из контейнеров, поскольку (как вы можете видеть в моем примере) они могут использовать шаблоны для обработки всех типов контейнеров.

    Исходя из этого логики, вполне можно спорить по алгоритмам M*N. Так что они также выполняют функции-члены (и вызывают внутренние функции)? Я уверен, что многие ребята из ООП предпочли бы

    auto result = container.accumulate(val);
    

    над

    auto result = std::accumulate(container.begin(), container.end(), val);
    
3
ответ дан Nawaz 26 August 2018 в 06:45
поделиться

Очевидно, что, как отметили другие пользователи, это сложная проблема, поэтому, к сожалению, это было долгое время, и в стандартной библиотеке все еще нет решения. Тем не менее, существуют уже имеющиеся библиотеки библиотек, такие как Boost :: Range и один в библиотечных библиотеках Adobe, которые обеспечивают не только простоту интерфейса, который вы описываете в своем вопросе, но и некоторые особенности fancier.

Ваш пример отлично работает с Boost (мы using namespace boost::range::adaptors ниже):

boost::for_each(myVector, doSomething);

Мы также можем быстро и легко срезать myVector:

boost::for_each(myVector | sliced(10, 20), doSomething)

Мы можем даже zip myVector с другим, фильтровать по предикату и пробовать каждый другой элемент результирующих пар в одном простом заявлении (для этого требуется, чтобы вы распаковывали в doSomethingElse кортежи, созданные boost::combined):

boost::for_each( boost::combined(myVector, myOtherVector) | strided(2), doSomethingElse)

0
ответ дан Nick Kent 26 August 2018 в 06:45
поделиться

Вот важный ответ из блога Herb Sutter: Почему нет алгоритмов на основе контейнеров . Он показывает контрпримеры, как это сделал Бо Перссон в своем ответе выше.

3
ответ дан Paul Jurczak 26 August 2018 в 06:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: