[英]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的头文件中。 因此,我想知道是否存在一种无需使用模板即可访问迭代器的替代方法。
有一些方法可以不将实现包括在头文件中,但是实现起来并不干净(例如,您应该事先知道实例化)。 在此处阅读有关此问题的更多信息:
例如在:
#ifndef HI_
#define HI_
template<class Iterator>
void foo(Iterator first, Iterator last);
#endif
#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
函数用于double
和int
。 其他类型将不会链接。
希望这可以帮助。
这是一个很长的答案。 简短的答案是“类型擦除”。 去了解它。
长答案是两个答案。 首先,我介绍“您是否只想能够对连续的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::vector
和std::string
和std::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.