繁体   English   中英

通用迭代器,无需使用Templates c即可访问向量的元素

[英]generic iterators to access elements of vectors without using Templates c++

我正在创建一个函数,应将其作为向量的输入迭代器,例如:

vector<int> a;
foo(a.begin(),a.end())

vector可以容纳任何类型。

现在,执行此操作的简单方法是使用模板

template <typename Iterator>
void foo(Iterator first, Iterator last) {
    for (Iterator it = first; it!=last; ++it) {
        cout << *it;
    }
}

我想知道是否有一种无需使用模板即可实现相同功能的方法。 因为使用模板会迫使我将这些函数包含在我不想使用的公共API的头文件中。 因此,我想知道是否存在一种无需使用模板即可访问迭代器的替代方法。

有一些方法可以不将实现包括在头文件中,但是实现起来并不干净(例如,您应该事先知道实例化)。 在此处阅读有关此问题的更多信息:

例如在:

foo.h

#ifndef HI_
#define HI_

template<class Iterator>
void foo(Iterator first, Iterator last);

#endif

foo.cpp

#include "stack.h"
using namespace std;


template<class Iterator>
void foo(Iterator first, Iterator last) {
    for (Iterator it = first; it != last; ++it) {
        cout << *it << " ";
    }
}

template
void foo( std::vector<int>::iterator  first,   std::vector<int>::iterator  last);

template
void foo( std::vector<double>::iterator  first,   std::vector<double>::iterator  last);

现在,您只能将foo函数用于doubleint 其他类型将不会链接。

希望这可以帮助。

这是一个很长的答案。 简短的答案是“类型擦除”。 去了解它。

长答案是两个答案。 首先,我介绍“您是否只想能够对连续的int进行迭代?”。 然后你要span 这是一种非常简单的类型擦除形式,只要它是连续的并且超过T ,就忘记了您正在使用的确切容器。

第二个答案是,如果您实际上需要处理多种类型(不仅仅是int )和多种容器(不仅仅是连续的容器)。

两个答案用一条线隔开。


正是出于这个原因,设计了span概念(请参阅gsl :: span )。 它本身是模板(取决于您使用的类型),但是在大多数接口中,它将是模板的具体实例。

这是它的玩具版本:

template<class T>
struct span_t {
  T* b = 0;
  T* e = 0;
  T* begin() const { return b; }
  T* end() const { return e; }
  span_t(span_t const&)=default;
  span_t& operator=(span_t const&)=default;
  span_t()=default;

  span_t( T* s, T* f ):b(s),e(f) {}
  span_t( T* s, std::size_t l):span_t(s, s+l){}
  template<std::size_t N>
  span_t( T(&arr)[N] ):span_t(arr, N) {}

  std::size_t size() const { return end()-begin(); }
  bool empty() const { return begin()==end(); }
  T& front() const { return *begin(); }
  T& back() const { return *(std::prev(end()); }
  T* data() const { return begin(); }

  span_t without_front( std::size_t N=1 ) const {
    return {std::next( begin(), (std::min)(N, size()) ), end()};
  }
  span_t without_back( std::size_t N=1 ) const {
    return {begin(), std::prev(end(), (std::min)(N, size()) )};
  }
};

我们可以用转换运算符来增加它

namespace details {
  template<template<class...>class Z, class, class...Ts>
  struct can_apply:std::false_type{};
  template<class...>using void_t=void;
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z,void,Ts...>;

template<class C>
using dot_data_r = decltype( std::declval<C>().data() );
template<class C>
using dot_size_r = decltype( std::declval<C>().size() );

template<class C>
using can_dot_data = can_apply< dot_data_r, C >;
template<class C>
using can_dot_size = can_apply< dot_size_r, C >;

can_dot_data通过SFINAE检测.data()是否对C类型的对象有效。

现在我们添加一个构造函数:

template<class T,
  std::enable_if_t<
    can_dot_data<T&>{}
    && can_dot_size<T&>{}
    && !std::is_same<std::decay_t<T>, span_t>{}
    , int
  > =0
>
span_t( T&& t ): span_t( t.data(), t.size() ) {}

涵盖了std::vectorstd::stringstd::array

您的函数现在看起来像:

void foo(span_t<int> s) {
  for (auto&& e:s)
    std::cout << s;
  }
}

配合使用:

std::vector<int> a;
foo(a);

现在,这仅适用于特定类型的连续容器。


假设这不是您想要的。 也许您确实需要解决多种类型的问题,并且您不想公开标头中的所有内容。

然后,您需要做的就是称为类型擦除。

您需要从提供的类型中找出所需的最少操作集。 然后,您需要编写包装程序,将这些操作“类型擦除”简化为“无类型”操作。

它放在标题或另一个帮助程序标题中。

在函数的界面中或在标题中间帮助程序中,您将传入的类型进行类型擦除,然后将类型擦除的类型传递到“真实”实现中。

类型擦除的一个示例是std::function 它几乎接受任何可以使用固定签名调用的内容,并将其转换为单个类型擦除的类型。 除了如何复制,销毁和调用该类型的实例外,所有其他事情都被“遗忘”或删除。

对于您的情况:

template <typename Iterator>
void foo(Iterator first, Iterator last) {
  for (Iterator it = first; it!=last; ++it) {
    cout << *it;
  }
}

我看到有两件事需要删除: 迭代和打印。

struct printable_view_t {
  void const* data = 0;
  void(*print_f)(std::ostream& os, void const*) = 0;

  explicit operator bool()const{return data;}
  printable_view_t() = default;
  printable_view_t(printable_view_t const&) = default;

  template<class T,
    std::enable_if_t<!std::is_same<T, printable_view_t>{}, int> =0
  >
  printable_view_t( T const& t ):
    data( std::addressof(t) ),
    print_f([](std::ostream& os, void const* pv){
      auto* pt = static_cast<T const*>(pv);
      os << *pt;
    })
  {}
  std::ostream& operator()(std::ostream& os)const {
    print_f(os, data);
    return os;
  }
  friend std::ostream& operator<<(std::ostream& os, printable_view_t p) {
    return p(os);
  }
};

printable_view_t是类型擦除“我可以打印”的示例。

void bar( printable_view_t p ) {
  std::cout << p;
}

void test_bar() {
  bar(7);
  bar(3.14);
  bar(std::string("hello world"));
}

我们要做的下一件事是类型擦除迭代。 这比较难,因为我们要在擦除printable_view_t类型上键入擦除迭代。

类型擦除foreach比较容易,而且通常更有效。

template<class View>
struct foreach_view_t {
  void* data = 0;
  void(*func)( std::function<void(View)>, void* ) = 0;

  explicit operator bool()const{return data;}
  foreach_view_t() = default;
  foreach_view_t(foreach_view_t const&) = default;

  template<class T,
    std::enable_if_t<!std::is_same<std::decay_t<T>, foreach_view_t>{}, int> =0
  >
  foreach_view_t( T&& t ):
    data( const_cast<std::decay_t<T>*>(std::addressof(t)) ),
    func([](std::function<void(View)> f, void* pv){
      auto* pt = static_cast<std::remove_reference_t<T>*>(pv);
      for (auto&& e : *pt)
        f(decltype(e)(e));
    })
  {}
  void operator()(std::function<void(View)> f)const{
    func(f, data);
  }
};

然后,我们将这些菊花链连接在一起

void foo(foreach_view_t<printable_view_t> x) {
  x([](auto p){ std::cout << p; });
}

测试代码:

std::vector<int> a{1,2,3};

foo(a);

现在,许多标头代码被“提升”到类型擦除类型中,而不是功能模板主体中。 但是,仔细选择类型擦除点可以使您精确而狭窄地保留所需的内容,以及如何私下使用这些操作的逻辑。

例如,上面的代码并不关心您将其打印到何处 std::cout不是类型擦除的一部分。

现场例子

我想知道是否有一种无需使用模板即可实现相同功能的方法。 [...]我想知道是否有另一种无需使用模板即可访问迭代器的方法。

是的,如果您使用C ++ 14,但是...

因为使用模板会迫使我将这些函数包含在我不想使用的公共API的头文件中。

...对您而言不是一种有用的方法,因为它等效于使用模板,并且您必须将其放在头文件中。

在C ++ 14中,您可以将lambda函数与auto参数一起使用。

auto foo = [](auto first, auto last)
 { for (auto it = first ; it != last; ++it ) std::cout << *it; };

auto并不是模板(从正式的角度来看),而是等效的,并且您不能在标头中声明foo并将其开发为cpp文件。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM