是否可以从迭代器类型中检测容器类型?
例如,
#include<traits>
int main(){
static_assert(std::is_same<
container_of<std::vector<double>::iterator>::type, std::vector<double>>{});
static_assert(std::is_same<
container_of<std::list<int>::iterator>::type, std::list<int>>{});
}
(当然,某些迭代器类型不会提供容器(或不会提供唯一的容器),例如原始指针或流迭代器,但在这些情况下,它可能会软SFINAE失败。)
第一次尝试是
template<class T, template<class> class Cont> Cont<T> aux(typename Cont<T>::iterator it);
template<class Iterator> struct container_of{
using type = decltype(aux(Iterator{}));
};
但是,它不起作用,因为编译器无法检测到
T
的类型(它不在可扣除的上下文中)。
动机:我想检测迭代器的关联容器是否有
.data()
成员。
不要让你的基元成为迭代器,而让你的基元成为一个范围。
template<class It, bool Contiguous, class D=void>
struct range_t {
using Self = std::conditional< !std::is_same<D, void>, D, range_t >;
It b, e;
It begin() const { return b; }
It end() const { return e; }
Self without_front( std::size_t i = 1 ) const {
return {std::next(begin(), i), end()};
}
Self without_back( std::size_t i = 1 ) const {
return {begin(), std::prev(end(), i)};
}
bool empty() const { return begin()==end(); }
std::size_t size() const { return std::distance( begin(), end() ); }
};
template<class It>
struct range_t<It, true, void>:
range_t<It, false, range_t<It, true>>
{
using Base = range_t<It, false, range_t<It, true>>;
range_t( It b, It e ):Base(b,e) {}
auto* data() const {
if (empty()) return nullptr;
return std::addressof(*this->begin()); }
}
};
(手动)跟踪哪些容器是连续的:
template<class T, class=void>
struct is_contiguous_container : std::false_type{};
template<class T>
struct is_contiguous_container<T const, void> : is_contiguous_container<T> {};
template<class T>
struct is_contiguous_container<T volatile, void> : is_contiguous_container<T> {};
template<class T>
struct is_contiguous_container<T const volatile, void> : is_contiguous_container<T> {};
template<class T>
struct is_contiguous_container<T, std::enable_if_t< has_data_ptr<T>{} >>:
std::true_type{};
template<class T, std::size_t N>
struct is_contiguous_container<T[N],void> : std::true_type{};
连续的容器是数组、
std::array
和std::vector
,所以不需要跟踪太多。 range_t< ?, true, ? >
也是连续的。 只要写 has_data_ptr
,当且仅当 T.data()
返回一个指向非 void 的指针时,这是正确的。
template<class C>
auto range( C&& c ) {
using std:begin; using std::end;
auto b = begin(c), e = end(c);
using It = decltype(b);
using R = range_t<It, is_contiguous_container<std::remove_reference_t<C>>{}>;
return R{ b, e };
}
range
现在可以智能地将容器转换为 range_t
,并跟踪它是否连续。
range_t
支持r.without_front( r.size()/2 )
分而治之。
当范围是连续的时,只需对其调用
.data()
即可。 如果不是,请不要。
在您的应用程序中,如果您只想知道容器是否有
.data()
成员,检查它是否是随机访问可能就足够了(使用 std::iterator_traits<Iter>::iterator_category()
)。
否则,我认为您可能可以结合使用以下技术:如何检查两种类型是否来自同一模板化类以及每个标准容器类型的部分专业化。
或者等待c++17,它有一个新的连续迭代器概念:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4284.html
我现在正在做的是手动注册所有(实际上是一些)连续的迭代器。
由于我总是需要结合某种方法来提取原始指针,因此我直接编写了一个名为
data
的函数来返回指针。
代码并不有趣,它考虑了
std::vector<>::iterator
,std::basric_string<>::iterator
,为了说明(以表明它总是不完整的)我还添加了boost::static_vector<>
,原始指针和任何可转换为指针的东西。 (boost::array<>::iterator
、std::array<>::iterator
和begin/end(std::valarray)
被有效地包含在内,因为迭代器是指针)。
我还必须包括 const_iterator
案例。
#include<type_traits>
#include<vector> // the code below needs to know about std::vector
#include<boost/container/static_vector.hpp> // ... and all possible contigous containers :(
template<
class ContiguousIterator, // well ProbablyContiguos
typename = std::enable_if_t<
/**/std::is_same<ContiguousIterator, typename std::vector<std::decay_t<decltype(*std::declval<ContiguousIterator>())>>::iterator>{}
or std::is_same<ContiguousIterator, typename std::vector<std::decay_t<decltype(*std::declval<ContiguousIterator>())>>::const_iterator>{}
or std::is_same<ContiguousIterator, typename std::basic_string<std::decay_t<decltype(*std::declval<ContiguousIterator>())>>::iterator>{}
or std::is_same<ContiguousIterator, typename boost::container::static_vector<std::decay_t<decltype(*std::declval<ContiguousIterator>())>, 1>::iterator>{}
or std::is_same<ContiguousIterator, typename boost::container::static_vector<std::decay_t<decltype(*std::declval<ContiguousIterator>())>, 1>::const_iterator>{}
// many many other possible iterators :(
or std::is_pointer<ContiguousIterator>{}
or std::is_constructible<typename std::iterator_traits<ContiguousIterator>::pointer, ContiguousIterator>{}
>
>
typename std::iterator_traits<ContiguousIterator>::pointer
data(ContiguousIterator const& it){return std::addressof(*it);}
int main(){
std::vector<double> v(30);
v[0] = 10.;
assert( *data(v.begin()) == 10. );
}
欢迎反馈。