Mar*_*ila 20 c++ templates projection visual-c++ visual-c++-2013
我正在尝试编写一个可以将a vector<T>
转换为a 的投影函数vector<R>
.这是一个例子:
auto v = std::vector<int> {1, 2, 3, 4};
auto r1 = select(v, [](int e){return e*e; }); // {1, 4, 9, 16}
auto r2 = select(v, [](int e){return std::to_string(e); }); // {"1", "2", "3", "4"}
Run Code Online (Sandbox Code Playgroud)
第一次尝试:
template<typename T, typename R>
std::vector<R> select(std::vector<T> const & c, std::function<R(T)> s)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}
Run Code Online (Sandbox Code Playgroud)
但对于
auto r1 = select(v, [](int e){return e*e; });
Run Code Online (Sandbox Code Playgroud)
我明白了:
错误C2660:'select':函数不带2个参数
我必须明确地打电话select<int,int>
去工作.我不喜欢这样,因为类型是多余的.
auto r1 = select<int, int>(v, [](int e){return e*e; }); // OK
Run Code Online (Sandbox Code Playgroud)
第二次尝试:
template<typename T, typename R, typename Selector>
std::vector<R> select(std::vector<T> const & c, Selector s)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}
Run Code Online (Sandbox Code Playgroud)
结果是同样的错误,函数不带2个参数.在这种情况下,我实际上必须提供第三种类型的参数:
auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });
Run Code Online (Sandbox Code Playgroud)
第三次尝试:
template<typename T, typename R, template<typename, typename> class Selector>
std::vector<R> select(std::vector<T> const & c, Selector<T,R> s)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}
Run Code Online (Sandbox Code Playgroud)
对于
auto r1 = select<int, int, std::function<int(int)>>(v, [](int e){return e*e; });
Run Code Online (Sandbox Code Playgroud)
错误是:
'select':'Selector'的无效模板参数,期望的类模板
对于
auto r1 = select(v, [](int e){return e*e; });
Run Code Online (Sandbox Code Playgroud)
错误C2660:'select':函数不带2个参数
(我知道最后两次尝试并不是特别好.)
如何编写此select()
模板函数以用于我在开头放入的示例代码?
Pio*_*cki 27
基本decltype()
用法:
template <typename T, typename F>
auto select(const std::vector<T>& c, F f)
-> std::vector<decltype(f(c[0]))>
{
using R = decltype(f(c[0]));
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), f);
return v;
}
Run Code Online (Sandbox Code Playgroud)
基本std::result_of<T>
用法:
template <typename T, typename F, typename R = typename std::result_of<F&(T)>::type>
std::vector<R> select(const std::vector<T>& c, F f)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), f);
return v;
}
Run Code Online (Sandbox Code Playgroud)
高级decltype()
用法和完美转发(见注*):
template <typename T, typename A, typename F>
auto select(const std::vector<T, A>& c, F&& f)
-> std::vector<typename std::decay<decltype(std::declval<typename std::decay<F>::type&>()(*c.begin()))>::type>
{
using R = typename std::decay<decltype(std::declval<typename std::decay<F>::type&>()(*c.begin()))>::type;
std::vector<R> v;
std::transform(std::begin(c), std::end(c)
, std::back_inserter(v)
, std::forward<F>(f));
return v;
}
Run Code Online (Sandbox Code Playgroud)
高级std::result_of<T>
用法和完美转发(见注*):
template <typename T, typename A, typename F, typename R = typename std::decay<typename std::result_of<typename std::decay<F>::type&(typename std::vector<T, A>::const_reference)>::type>::type>
std::vector<R> select(const std::vector<T, A>& c, F&& f)
{
std::vector<R> v;
std::transform(std::begin(c), std::end(c)
, std::back_inserter(v)
, std::forward<F>(f));
return v;
}
Run Code Online (Sandbox Code Playgroud)
*注:选项#3和#4假设std::transform
算法需要一个功能对象按值,然后使用它作为一个非const左值.这就是人们可以看到这种奇怪typename std::decay<F>::type&
语法的原因.如果函数对象应该在select
函数本身内被调用,并且结果类型不会被用作容器的模板参数(为了使用最外层 的那个std::decay<T>
),那么正确的可移植语法获取返回类型是:
/*#3*/ using R = decltype(std::forward<F>(f)(*c.begin()));
/*#4*/ typename R = typename std::result_of<F&&(typename std::vector<T, A>::const_reference)>::type
Run Code Online (Sandbox Code Playgroud)
Yak*_*ont 11
你的第一个问题是你认为lambda是一个std::function
.A std::function
和lambda是不相关的类型. std::function<R(A...)>
是一个类型擦除对象,可以转换任何(A)可复制,(B)可销毁和(C)可以使用A...
和返回兼容的类型R
,并删除有关该类型的所有其他信息.
这意味着它可以消耗完全不相关的类型,只要它们通过这些测试即可.
lambda是一个可破坏的匿名类,可以复制(除了在C++ 14中,有时这是有的),并且有一个operator()
你指定的.这意味着您通常可以将lambda转换为std::function
具有兼容签名的lambda .
演绎着std::function
从拉姆达是不是一个好主意(有办法做到这一点,但他们是坏的想法:C++ 14个auto
.lambda表达式打破他们,再加上你不用无效)
那么我们如何解决您的问题呢?在我看来,你的问题是获取一个函数对象和一个容器,并推断transform
在每个元素上应用函数对象后会产生什么样的元素,因此你可以将结果存储在一个std::vector
.
这是最接近问题解决方案的答案:
template<typename T, typename R, typename Selector>
std::vector<R> select(std::vector<T> const & c, Selector s) {
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}
Run Code Online (Sandbox Code Playgroud)
最简单的方法是交换T
和R
按模板顺序,并让调用者R
明确地传递,如select<double>
.这使得T
与Selector
被推断出来.这并不理想,但确实做了一点改进.
对于完整的解决方案,有两种方法可以解决此解决方案.首先,我们可以更改select
为返回一个带有的临时对象operator std::vector<R>
,将转换延迟到该点.这是一个不完整的草图:
template<typename T, typename Selector>
struct select_result {
std::vector<T> const& c;
Selector s;
select_result(select_result&&)=default;
select_result(std::vector<T> const & c_, Selector&& s_):
c(c_), s(std::forward<Selector>(s_)
{}
operator std::vector<R>()&& {
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}
};
template<typename T, typename Selector>
select_result<T, Selector> select(std::vector<T> const & c, Selector&& s) {
return {c, std::forward<Selector>(s)};
}
Run Code Online (Sandbox Code Playgroud)
我还可以提供一个可靠的依赖于未定义行为的光滑版本(函数中本地引用的引用捕获在标准下具有生命周期问题).
但这摆脱了auto v = select
语法 - 你最终存储产生结果的东西,而不是结果.
你仍然可以做到std::vector<double> r = select( in_vec, [](int x){return x*1.5;} );
并且效果很好.
基本上我将演绎拆分为两个阶段,一个用于参数,一个用于返回值.
但是,没有必要依赖该解决方案,因为还有其他更直接的方法.
对于第二种方法,我们可以推断R
自己:
template<typename T, typename Selector>
std::vector<typename std::result_of<Selector(T)>::type>
select(std::vector<T> const & c, Selector s) {
using R = typename std::result_of<Selector(T)>::type;
std::vector<R> v;
std::transform(std::begin(c), std::end(c), std::back_inserter(v), s);
return v;
}
Run Code Online (Sandbox Code Playgroud)
这是一个非常可靠的解决方案.清理一下:
// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&;
template<
typename T, typename A,
typename Selector,
typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(std::vector<T, A> const & c, Selector&& s) {
std::vector<R> v;
std::transform(begin(c), end(c), back_inserter(v), std::forward<Selector>(s));
return v;
}
Run Code Online (Sandbox Code Playgroud)
使这成为一个可用的解决方案.(移动R
到template
类型列表,允许替代分配器vector
,删除一些不必要的std::
,并做了完美的转发Selector
).
但是,我们可以做得更好.
输入是a的事实vector
是毫无意义的:
template<
typename Range,
typename Selector,
typename R=typename std::result_of<Selector(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
std::vector<R> v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
return v;
}
Run Code Online (Sandbox Code Playgroud)
因为无法确定而无法编译T
.所以让我们继续努力:
namespace details {
namespace adl_aux {
// a namespace where we can do argument dependent lookup on begin and end
using std::begin; using std::end;
// no implementation, just used to help with ADL based decltypes:
template<class R>
decltype( begin( std::declval<R>() ) ) adl_begin(R&&);
template<class R>
decltype( end( std::declval<R>() ) ) adl_end(R&&);
}
// pull them into the details namespace:
using adl_aux::adl_begin;
using adl_aux::adl_end;
}
// two aliases. The first takes a Range or Container, and gives
// you the iterator type:
template<class Range>
using iterator = decltype( details::adl_begin( std::declval<Range&>() ) );
// the second is syntactic sugar on top of `std::iterator_traits`:
template<class Iterator>
using value_type = typename std::iterator_traits<Iterator>::value_type;
Run Code Online (Sandbox Code Playgroud)
这给了我们一个iterator<Range>
和value_type<Iterator>
别名.他们一起让我们T
轻松推断:
// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&;
template<
typename Range,
typename Selector,
typename T=value_type<iterator<Range&>>,
typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
std::vector<R> v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
return v;
}
Run Code Online (Sandbox Code Playgroud)
和鲍勃是你的叔叔.(decayed_lvalue
反映了Selector
类型如何用于极端情况,并iterator<Range&>
反映我们从左值版本获得迭代器Range
).
在VS2013中,有时候上面decltype
的内容会混淆他们拥有的C++ 11的半实现.iterator<Range>
用decltype(details::adl_begin(std::declval<Range>()))
那样丑陋的替换可以解决这个问题.
// std::transform takes by-value, then uses an lvalue:
template<class T>
using decayed_lvalue = typename std::decay<T>::type&;
template<
typename Range,
typename Selector,
typename T=value_type<decltype(details::adl_begin(std::declval<Range&>()))>,
typename R=typename std::result_of<decayed_lvalue<Selector>(T)>::type
>
std::vector<R> select(Range&& in, Selector&& s) {
std::vector<R> v;
using std::begin; using std::end;
std::transform(begin(in), end(in), back_inserter(v), std::forward<Selector>(s));
return v;
}
Run Code Online (Sandbox Code Playgroud)
生成的函数将采用数组,向量,列表,映射或自定义编写的容器,并将采用任何转换函数,并生成结果类型的向量.
下一步是使转换变得懒惰,而不是直接将其转换为vector
.as_vector
如果你需要摆脱懒惰的评估,你可以拥有一个范围并将其写入向量.但这就是编写整个库而不是解决问题.