从迭代器推导出容器类型(如果可能)

时间:2017-03-23 07:03:40

标签: c++11 iterator containers sfinae typetraits

是否可以从迭代器类型中检测容器类型?

例如,

#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()成员。

3 个答案:

答案 0 :(得分:1)

不要将原语作为迭代器,而应将原语作为范围。

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::arraystd::vector,所以跟踪不多。 range_t< ?, true, ? >也是连续的。只需编写has_data_ptr,如果T.data()返回指向非void的指针,则为true。

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()即可。如果不是,请不要。

答案 1 :(得分:0)

在您的应用程序中,如果您只想知道容器是否有.data()成员,则可能足以检查它是否是随机访问(使用std::iterator_traits<Iter>::iterator_category())。

否则,我认为您可以在How to check if two types come from the same templated class中使用该技术的组合,并为每个标准容器类型使用部分特化。

或等待具有新的连续迭代器概念的c ++ 17:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4284.html

答案 2 :(得分:0)

我目前正在做的是手动注册所有(实际上)连续的迭代器。

由于我总是需要这种方法来提取原始指针,我直接编写一个名为data的函数来返回指针。

代码不好笑,它会考虑std::vector<>::iteratorstd::basric_string<>::iterator来说明(表明它总是不完整)我还添加了boost::static_vector<>,原始指针和任何可转换的内容一个指针。 (boost::array<>::iteratorstd::array<>::iteratorbegin/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. );
}

欢迎反馈。