因此,这里有许多要处理的细节问题。我首先尝试构建一些
container_traits
模板,以尽可能抽象出大部分工作。
如果一个类型允许调用
begin
和
end
自由函数,并通过
using
引入了
std::begin
和
std::end
,并且这两个类型相同(最后一个可能不是必需的),则该类型是一个
container
。
container
的特性主要来自于容器具有的
iterator
及其类型,再加上像
size
(甚至是
size_at_least
- 请参见下文)等常见特征。
如果一个类型的
const
是一个
container
,则称该类型为可迭代的
iterable
。
接下来的问题是,“哪种类型实例可以用于映射容器的元素?”这也稍微有点复杂,因此我添加了一些特征类来处理它。
因此,这导致了以下实现:
#include <algorithm>
#include <type_traits>
#include <utility>
namespace aux {
using std::begin;
using std::end;
template<typename T>
auto adl_begin(T&&t)->decltype( begin(std::forward<T>(t)) );
template<typename T>
auto adl_end(T&&t)->decltype( end(std::forward<T>(t)) );
template<typename T>
auto adl_cbegin(T const&t)->decltype( begin(t) );
template<typename T>
auto adl_cend(T const&t)->decltype( end(t) );
}
template<typename C,typename=void>
struct is_container:std::false_type {};
template<typename C>
struct is_container<C, typename std::enable_if<
std::is_same<
decltype(aux::adl_begin(std::declval<C>())),
decltype(aux::adl_end(std::declval<C>()))
>::value
>::type >:std::true_type {};
template<typename C, typename=void>
struct container_traits {};
template<typename C>
struct container_traits<C, typename std::enable_if< is_container<C>::value >::type >
{
typedef decltype( aux::adl_begin(std::declval<C>()) ) iterator;
typedef decltype( aux::adl_cbegin(std::declval<C>()) ) const_iterator;
typedef typename std::iterator_traits<iterator>::value_type value_type;
typedef typename std::iterator_traits<iterator>::reference reference;
};
template<typename C, typename Functor, typename=void>
struct can_map:std::false_type {};
template<typename C, typename Functor>
struct can_map<C, Functor, typename std::enable_if<
!std::is_same< decltype(std::declval<Functor>()(std::declval<typename container_traits<C>::value_type>())), void >::value
>::type>: std::true_type {};
template<typename C, typename Functor, typename=void>
struct map_result {};
template<typename C, typename Functor>
struct map_result<C,Functor,typename std::enable_if< can_map<C,Functor>::value>::type>
{
typedef
decltype(
std::declval<Functor>()(
*std::declval<
typename container_traits<C>::const_iterator
>()
)
)
type;
};
template <typename T, typename Func>
auto map_container(T&& iterable, Func&& func) ->
std::vector<
typename std::decay<
typename map_result<T, Func>::type
>::type
>
{
std::vector<
typename std::decay<
typename map_result<T, Func>::type
>::type
> retval;
for (auto&& s:iterable) {
retval.push_back( func(s) );
}
return retval;
}
这就是全部内容了。接下来是测试代码。我们应该能够在C风格数组、传统类型和布尔值(使用伪引用并紧密打包位)的
vector
s以及用户定义类型上映射
map_container
,无论是通过
.begin()
方法还是通过自由浮动的
begin(C)
函数。
我在处理数组时遇到的一个问题是,
C const&
似乎会导致指针衰减,使其不再是容器:我必须绑定到
C&&
才能获得真正的数组类型。
#include <iostream>
void test1() {
std::vector<int> src{1,2,3,4,5};
auto r = map_container( src, [](int x){return x*2;});
for (auto&& x:r) {
std::cout << x << "\n";
}
}
struct test_buffer {
int foo[5];
int* begin() { return foo; }
int* end() { return &foo[5]; }
int const* begin() const { return foo; }
int const* end() const { return &foo[5]; }
};
test_buffer buff1={{1,2,3,4,5}};
struct test_buffer_2 {
int foo[5];
};
test_buffer_2 buff2={{1,2,3,4,5}};
int* begin(test_buffer_2& t) { return t.foo; }
int* end(test_buffer_2& t) { return &t.foo[5]; }
int const* begin(test_buffer_2 const& t) { return t.foo; }
int const* end(test_buffer_2 const& t) { return &t.foo[5]; }
std::vector<bool> bits{true, false, true, false};
template<typename Container>
void tester(Container&& c) {
Container const& src = c;
auto r = map_container( src, [](int x){return x*2;});
for (auto&& x:r) {
std::cout << x << "\n";
}
}
void test2() {
tester(buff1);
tester(buff2);
tester(bits);
}
template<typename C>
bool is_container_test(C&&) {
return is_container<C>::value;
}
template<typename C, typename F>
bool can_map_test( C&&, F&& ) {
return can_map<C, F>::value;
}
template<typename C, typename F>
bool can_map_test2( C const&, F&& ) {
return can_map<C, F>::value;
}
int array[] = {1,2,3,4,5};
void test3() {
std::cout << "Array is container:" << is_container_test(array) << "\n";
auto x2 = [](int x){return x*2;};
std::cout << "Double can map:" << can_map_test(array, x2) << "\n";
std::cout << "Double can map:" << can_map_test2(array, x2) << "\n";
}
void test4() {
tester(array);
}
int main() {
test1();
test2();
test3();
test4();
}
或者类似的东西。在函数本身中不要使用复杂的SFINAE,而是创建能为您完成工作的特征类。
上面使用的其他技术:我使用了std::begin
和std::end
来获取起始/结束迭代器。这意味着现在支持原始C数组。然后我将其包装在一些参数相关的查找助手中,其目的是允许您在相同的命名空间中定义begin
和end
的类覆盖。
请注意,“container_traits”的“无接受”版本是一个空结构体,而不是未定义的结构体。这使我们可以在其他地方使用container_traits
中的SFINAE。
哦,还有一个效率提高的方法是编写“智能储备”,它接受具有reserve
方法和您希望复制大小的容器。如果您想要复制的容器缺少随机访问迭代器并且缺少.size()
方法,则不执行任何操作,但如果它确实存在,则执行.reserve(end(...) - begin(...))
或.reserve(src.size())
。我们可以通过将其添加到container_traits
中作为static size_t size_at_least(Container const&)
来为其他算法抽象,该函数以O(1)时间返回不大于Container
大小的size_t
。
decltype(func( *(iterable.cbegin())))
替换掉它。 - NtscCobalt