[英]Templated Function that works for iterators over raw pointers as well as iterators over unique_ptrs
Let's say I have a template function that takes a const range (or better, begin- and end-iterators) of some kind of pointer-collections. 假设我有一个模板函数,它接受某种指针集合的const范围(或更好的开始和结束迭代器)。 This function internally constructs a STL-container with pointers to reorganize the elements.
此函数在内部构造一个STL容器,其中包含指针以重新组织元素。
Now I want to reuse this function for unique_ptr-collections as well. 现在我想为unique_ptr-collections重用这个函数。 I somehow need to modify the template parameters or introduce a new wrapper or overload... but how?
我不知何故需要修改模板参数或引入新的包装器或重载......但是如何? Is there any C++11 template magic, STL helper or boost helper?
有没有C ++ 11模板魔术,STL助手或助推助手? Following an example code:
下面是一个示例代码:
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
// Element Class
class Foo { };
// Take a range of elements, sort them internally by their addresses and print them in order
template <typename FooIterator>
void print_sorted_addresses(FooIterator beginFoos, FooIterator endFoos)
{
// Sort them
std::vector<const Foo*> elements(beginFoos, endFoos);
std::sort(elements.begin(), elements.end());
// Print them
for(const auto& e : elements)
std::cout << e << std::endl;
}
int main() {
std::vector<Foo*> raw_foos;
std::vector<std::unique_ptr<Foo>> unique_foos;
// Fill them
for(int i=0; i<10; i++) {
std::unique_ptr<Foo> foo(new Foo());
raw_foos.push_back(foo.get());
unique_foos.push_back(std::move(foo));
}
print_sorted_addresses(raw_foos.cbegin(), raw_foos.cend());
//print_sorted_Foos(unique_foos.cbegin(), unique_foos.cend()); // ERROR
return 0;
}
The culprit seems to be the non-uniform behavior of raw pointers and smart pointers ( unique_ptr
in particular) for converting them both to raw pointers. 罪魁祸首似乎是原始指针和智能指针(特别是
unique_ptr
)的非均匀行为,用于将它们转换为原始指针。 This can either be circumvented via a dereferencing-cycle à la std::addressof(*p)
, but this only has well-defined behavior if p is not nullptr
. 这可以通过dereferencing-
std::addressof(*p)
来规避,但是如果p不是nullptr
那么它只有明确定义的行为。 To reduce any runtime-checks I played with conditional templates and came up with the following: 为了减少我使用条件模板进行的任何运行时检查,并提出以下内容:
template<typename Ptr> using RawPtr = typename std::pointer_traits<Ptr>::element_type*;
// raw pointers like int**, const char*, ...
template<typename Ptr>
typename std::enable_if<std::is_pointer<Ptr>::value, RawPtr<Ptr>>::type make_raw(Ptr ptr) { return ptr; }
// smart pointers like unique_ptr, shared_ptr, ...
template<typename Ptr>
typename std::enable_if<!std::is_pointer<Ptr>::value, RawPtr<Ptr>>::type make_raw(Ptr& ptr) { return ptr.get(); }
This could be used in @tclamb's iterator, or in boost::transform_iterator as in @Praetorian's answer. 这可以在@tclamb的迭代器中使用,或者在@ Praetorian的答案中用于boost :: transform_iterator。 But it still feels strange to build upon the specific get()-member of a smart-pointer implementation instead of the operator*-interface what makes a pointer a pointer.
但是构建特定的get() - 智能指针实现的成员而不是操作符* -interface使得指针成为指针仍然感觉很奇怪。
Here's a generic approach that wraps the pointer iterator. 这是一个包装指针迭代器的通用方法。 On dereference, it dereferences the stored iterator (yielding the (smart-)pointer), and dereferences again (yielding a reference to the pointee ), and then returns the pointee's address (via
std::addressof()
). 在取消引用时,它取消引用存储的迭代器(产生(smart-)指针),并再次取消引用(产生对指针对象的引用),然后返回指针对象的地址(通过
std::addressof()
)。 The rest of the implementation is just iterator boilerplate. 其余的实现只是迭代器样板。
template<typename Iterator,
typename Address = decltype(std::addressof(**std::declval<Iterator>()))
>
class address_iterator : public std::iterator<std::input_iterator_tag, Address>
{
public:
address_iterator(Iterator i) : i_{std::move(i)} {};
Address operator*() const {
auto&& ptr = *i_;
return i_ == nullptr ? nullptr : std::addressof(*ptr);
};
Address operator->() const {
return operator*();
}
address_iterator& operator++() {
++i_;
return *this;
};
address_iterator operator++(int) {
auto old = *this;
operator++();
return old;
}
bool operator==(address_iterator const& other) const {
return i_ == other.i_;
}
private:
Iterator i_;
};
template<typename I, typename A>
bool operator!=(address_iterator<I, A> const& lhs, address_iterator<I, A> const& rhs) {
return !(lhs == rhs);
}
template<typename Iterator>
address_iterator<Iterator> make_address_iterator(Iterator i) {
return i;
}
Live example on Coliru (with a std::random_shuffle()
thrown in for fun). Coliru上的实例 (带有
std::random_shuffle()
以获得乐趣)。 :) :)
The problem with your code when dealing with unique_ptr
is this line: 处理
unique_ptr
时代码的问题是这一行:
std::vector<const Foo*> elements(beginFoos, endFoos);
The vector
constructor is going to try and copy the unique_ptr
s, which is not allowed; vector
构造函数将尝试复制unique_ptr
,这是不允许的; and you're interested in what the unique_ptr
points to anyway. 并且你对
unique_ptr
指向的内容感兴趣。 So you need an extra level of dereferencing to yield a reference to the managed object. 因此,您需要额外级别的解除引用才能生成对托管对象的引用。 This can be achieved using Boost.IndirectIterator .
这可以使用Boost.IndirectIterator来实现。
Using boost::indirect_iterator
will yield Foo const&
, which can then be converted to Foo const *
by wrapping it in Boost.TransformIterator , and passing std::addressof
as the unary predicate to boost::transform_iterator
. 使用
boost::indirect_iterator
将产生Foo const&
,然后可以通过将其包装在Boost.TransformIterator中将其转换为Foo const *
,并将std::addressof
作为一元谓词传递给boost::transform_iterator
。
template <typename FooIterator>
void print_sorted_addresses(FooIterator beginFoos, FooIterator endFoos) {
std::vector<Foo const *> elements(
boost::make_transform_iterator(boost::make_indirect_iterator(beginFoos),
std::addressof<Foo>),
boost::make_transform_iterator(boost::make_indirect_iterator(endFoos),
std::addressof<Foo>));
std::sort(elements.begin(), elements.end());
for(const auto& e : elements)
std::cout << e << std::endl;
}
My 2 coins 我的2个硬币
Foo* get(Foo* const& p) {
return p;
}
Foo* get(std::unique_ptr<Foo> const& up) {
return up.get();
}
// Take a range of elements, sort them internally by their addresses and print
// them in order
template <typename ConstIt>
void print_sorted_addresses(const ConstIt& cbegin, const ConstIt& cend) {
using deref_type = decltype(*cbegin);
using raw_ptr_type = decltype(get(*cbegin));
std::vector<raw_ptr_type> v;
v.reserve(cend - cbegin);
std::transform(cbegin, cend,
std::back_inserter(v),
[] (const deref_type& p) {
return get(p);
});
std::sort(v.begin(), v.end());
for(const auto& p : v)
std::cout << p << '\n';
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.