1

C++ std 算法定义了许多算法,它们接受输入和输出序列,并根据输入序列的元素创建输出序列的元素。(最好的例子是std::transform。)

std 算法显然采用iterators,因此毫无疑问,在OutputIterator调用算法之前,容器必须存在。

那是:

std::vector<int> v1; // e.g. v1 := {1, 2, 3, 4, 5};

std::vector<int> squared;
squared.reserve(v1.size()); // not strictly necessary
std::transform(v1.begin(), v1.end(), std::back_inserter(squared), 
               [](int x) { return x*x; } ); // λ for convenience, needn't be C++11

就 std 库而言,这很好。当我发现迭代器太麻烦时,我经常使用Boost.Range来简化事情。

然而,在这种情况下,Boost.Range 中的变异算法似乎 使用OutputIterators.

所以我目前想知道那里是否有任何方便的库,可以让我写:

std::vector<int> const squared = convenient::transform(v1, [](int x) { return x*x; });

- 如果没有,是否有理由没有

编辑:示例实现(不确定这是否适用于所有情况,以及这是否是最理想的):

template<typename C, typename F>
C transform(C const& input, F fun) {
   C result;
   std::transform(input.begin(), input.end(), std::back_inserter(result), fun);
   return result;
}       

(注意:我认为convenient::transform与手写的具有相同的性能特征,因为返回的向量不会因 (N)RVO 而被复制。无论如何,我认为性能对于这个问题是次要的。)


编辑/注意:到目前为止给出的答案(评论,真的)中,大卫给出了一个非常好的基本通用示例。

Luc 提到std::back_inserterwrt可能存在的问题。通用性。

两者都只是说明为什么我犹豫要自己动手,以及为什么“适当的”(经过适当测试的)库比自己编写代码更可取。

我上面用粗体字表示的问题,即是否有一个,或者是否有理由没有,基本上没有得到回答。

4

5 回答 5

3

这并不是对问题本身的回答,而是对其他答案的补充——但它不适合评论。

好吧 - 如果你想要 list 或 deque 或其他一些序列类型的容器怎么办 - 这是非常有限的。

namespace detail {

template<typename Iter, typename Functor>
struct transform {
    Iter first, last;
    Functor functor;

    template<typename Container> // SFINAE is also available here
    operator Container()
    {
        Container c;
        std::transform(first, last, std::back_inserter(c), std::forward<Functor>(functor));
        return c;
    }
};

} // detail

template<typename Iter, typename Functor>
detail::transform<Iter, typename std::decay<Functor>::type>
transform(Iter first, Iter last, Functor&& functor)
{ return { first, last, std::forward<Functor>(functor) }; }

虽然这适用于少数容器,但它仍然不是非常通用,因为它要求容器与std::back_inserter(c)(BackInsertable?)“兼容”。可能您可以使用 SFINAE 来代替使用std::inserterwith c.begin()if c.push_back()is not available(留给读者作为练习)。

所有这些还假设容器是 DefaultConstructible ——考虑使用范围分配器的容器。据推测失去通用性是一个特征,因为我们只是试图涵盖“最简单”的用途。

事实上,虽然我不会使用这样的库:我不介意在算法旁边创建容器以分离关注点。(我想这可以被认为是我对这个问题的回答。)

于 2011-08-26T11:46:29.733 回答
2

恕我直言,这种算法的重点是通用的,即主要与容器无关。您提议的是该transform函数非常具体,并返回一个std::vector, 好吧 - 如果您想要listdeque其他一些序列类型容器 - 它非常有限。

如果你觉得它很烦人,为什么不换行呢?创建您自己的小实用程序标题来执行此操作 - 毕竟,这很简单......

于 2011-08-26T10:25:54.810 回答
1

没有一种正确的启用方式

std::vector<int> const squared = 
             convenient::transform(v1, [](int x) { return x*x; });

没有潜在的性能成本。你要么需要一个明确的

std::vector<int> const squared = 
             convenient::transform<std::vector> (v1, [](int x) { return x*x; });

注意容器类型的明确提及:迭代器不告诉任何关于它们所属的容器的信息。如果您提醒标准允许容器的迭代器是普通指针,这将变得很明显。

让算法采用容器而不是迭代器也不是解决方案。这样,算法就无法知道如何正确获取第一个和最后一个元素。例如,long int-array 没有 , 和 的方法begin()end()并非length()所有容器都有随机访问迭代器,operator[]未定义。所以没有真正通用的方式来获取容器。


允许与容器无关的容器返回算法的另一种可能性是某种通用工厂(参见http://ideone.com/7d4E2的直播):

// (not production code; is even lacking allocator-types)
//-- Generic factory. -------------------------------------------
#include <list>
template <typename ElemT, typename CacheT=std::list<ElemT> >
struct ContCreator {

    CacheT cache; // <-- Temporary storage.

    // Conversion to target container type.
    template <typename ContT>
    operator ContT () const {
        // can't even move ...
        return ContT (cache.begin(), cache.end());
    }
};

除了模板化的演员表操作符之外,没有太多的魔力。然后你从你的算法中返回那个东西:

//-- A generic algorithm, like std::transform :) ----------------
ContCreator<int> some_ints () {
    ContCreator<int> cc;
    for (int i=0; i<16; ++i) {
        cc.cache.push_back (i*4);
    }
    return cc;
}

最后像这样使用它来编写魔术代码:

//-- Example. ---------------------------------------------------
#include <vector>
#include <iostream>
int main () {
    typedef std::vector<int>::iterator Iter;
    std::vector<int> vec = some_ints();
    for (Iter it=vec.begin(), end=vec.end(); it!=end; ++it) {
        std::cout << *it << '\n';
    }
}

如您所见,operator T其中有一个范围副本。

如果目标容器和源容器属于同一类型,则可以通过模板特化进行移动。

编辑:正如大卫指出的那样,您当然可以在转换运算符中进行真正的工作,这可能不需要额外的成本(做更多的工作可以更方便地完成;这只是为了演示):

#include <list>
template <typename ElemT, typename Iterator>
struct Impl {
    Impl(Iterator it, Iterator end) : it(it), end(end) {}

    Iterator it, end;

    // "Conversion" + Work.
    template <typename ContT>
    operator ContT () {
        ContT ret;
        for ( ; it != end; ++it) {
            ret.push_back (*it * 4);
        }
        return ret;    
    }
};

template <typename Iterator>
Impl<int,Iterator> foo (Iterator begin, Iterator end) {
    return Impl<int,Iterator>(begin, end);
}

#include <vector>
#include <iostream>
int main () {
    typedef std::vector<int>::iterator Iter;

    const int ints [] = {1,2,4,8};
    std::vector<int> vec = foo (ints, ints + sizeof(ints) / sizeof(int));

    for (Iter it=vec.begin(), end=vec.end(); it!=end; ++it) {
        std::cout << *it << '\n';
    }
}

一个要求是目标有一个push_back方法。如果目标容器迭代器不是随机访问的,则使用std::distance保留大小可能会导致性能欠佳。

于 2011-08-26T10:33:03.623 回答
1

同样,没有答案,而是从评论到另一个答案的跟进

关于问题代码中返回类型的通用性

目前的代码不允许转换返回类型,但可以通过提供两个模板轻松解决:

template <typename R, typename C, typename F>
R transform( C const & c, F f ) {_
    R res;
    std::transform( c.begin(), c.end(), std::back_inserter(res), f );
    return res;
}
template <typename C, typename F>
C transform( C const & c, F f ) {
    return transform<C,C,F>(c,f);
}
std::vector<int> src;
std::vector<int> v = transform( src, functor );
std::deque<int>  d = transform<std::deque<int> >( src, functor );
于 2011-08-26T12:40:20.300 回答
1

Boost.Range.Adaptors可以看作是容器返回算法为什么不使用它们?

唯一需要做的就是定义一个新的范围适配器create<T>,它可以通过管道传输到适应的范围并产生所需的结果容器:

template<class T> struct converted{}; // dummy tag class

template<class FwdRange, class T>
T operator|(FwdRange const& r, converted<T>){
  return T(r.begin(), r.end());
}

是的,就是这样。不需要其他任何东西。只需将其放在适配器列表的末尾即可。

可能是 Ideone 上的一个活生生的例子。唉,它不是,因为 Ideone 在 C++0x 模式下不提供 Boost .. 嗯。无论如何,这是main和输出:

int main(){
  using namespace boost::adaptors;
  auto range = boost::irange(1, 10);
  std::vector<int> v1(range.begin(), range.end());

  auto squared = v1 | transformed([](int i){ return i * i; });
  boost::for_each(squared, [](int i){ std::cout << i << " "; });
  std::cout << "\n========================\n";
  auto modded = squared | reversed
                        | filtered([](int i){ return (i % 2) == 0; })
                        | converted<std::vector<int>>(); // gimme back my vec!
  modded.push_back(1);
  boost::for_each(modded, [](int i){ std::cout << i << " "; });
}

输出:

1 4 9 16 25 36 49 64 81
========================
64 36 16 4 1
于 2012-02-09T05:36:54.887 回答