如何获得以下函数to_vector
的正确类型T
?
template<typename K> struct A { K* p; size_t n; std::string foo; };
template<typename K> struct B { K* p; size_t n; float bar[3]; };
template<typename X>
std::vector<T> to_vector(const X& x) { // what is T?
return std::vector<T>(x.p, x.p+x.n);
}
我尝试使用decltype(*std::declval<X>().p)
但这会导致以下示例error: forming pointer to reference type ‘float&’
:
A<float> a = { new float[10], 10, "hello" };
std::vector<float> v = to_vector(a);
这是一些更大代码的一部分,还有更多类型,如A
和B
.但是都有一个指针p
和一个长度n
.
你可能可以使用
typename std::decay<decltype(*X::p)>::type
T
因为decltype
是一个未评估的上下文,因此X::p
在这里是合法的。此外,std::decay
似乎很合适,因为它结合了std::remove_reference
和std::remove_cv
.
你走在正确的轨道上,你只需要使用适当的实用程序来摆脱指针/引用。
有std::remove_reference
,您可以像这样使用:
typename std::remove_reference<decltype(*std::declval<X>().p)>::type
但是std::remove_pointer
起来更简单一些:
typename std::remove_pointer<decltype(std::declval<X>().p)>::type
(看,*
消失了,否则相同)。
如果指针可能符合cv条件,您可能希望将 std::remove_cv 放入混合中,因为矢量元素不应该是。
如另一个现已删除的答案中所述,如果您使用尾随返回类型声明,则可以编写x.p
而不是std::declval<X>().p
:
template <typename X>
auto to_vector(const X& x) ->
std::vector<typename std::remove_pointer<decltype(x.p)>::type>
这个答案现在几乎已经过时了。
template<typename X, typename T = typename std::remove_reference<decltype(*X().p)>::type>
std::vector<T> to_vector(const X& x)
{
return std::vector<T>
(x.p, x.p+x.n);
}
所以这是一个完全超出左边的答案。
应通过将begin
和end
的命名空间中重载和B
A
类型转换为可迭代对象。
有多种方法可以做到这一点:
1) 强制它们中的每一个都实现一个成员begin
和成员end
,该成员返回指针作为迭代器。 或者,一个自由函数begin
和end
做同样的事情。
2) 你要求它们继承自执行上述操作的 CRTP 帮助程序类 - 它要么为你实现begin
和end
,要么启用一个自由函数begin
和end
ADL 可以看到的重载。
3)如果所有这些类都在您控制的某个namespace
中,并且您希望将K* p
和std::size_t n
字段视为应将其视为可迭代范围的证据,那么我们可以使用"全局"begin
和end
来执行此操作,该使用SFINAE仅适用于该情况。
我会建议#1或#2。
对于#2:
template<typename Derived>
struct p_n_iterable {
Derived* self() {
static_assert( std::is_base_of<p_n_iterable, Derived>::value, "CRTP failure" );
return static_cast<Derived*>(this);
}
Derived const* self() const {
static_assert( std::is_base_of<p_n_iterable, Derived>::value, "CRTP failure" );
return static_cast<Derived const*>(this);
}
typedef typename std::decay< decltype( *Derived::p ) >::type value_type;
typedef value_type* iterator;
std::size_t size() const { return self()->n; }
iterator begin() { return self->p; }
iterator end() { return begin() + size(); }
};
如果我写的没错,请将A
改为:
template<typename K> struct A : p_n_iterable<A<K>> { ... unchanged ... };
突然之间,for( auto x:a )
型循环在A
上工作.
我认为必须将小序言添加到类中的成本对于该功能来说是值得的。
为了使用 #3 执行此操作,我们创建了一个traits
类,通过检查T::n
是否属于std::size_t
类型以及T::p
是指针类型来检测是否应该n_p_iterable
它。 我建议不要这样做,因为虽然它在其他地方需要较少的样板,但它非常笨拙。
一旦有了这个,我们就可以写一个非常通用的to_vector
.
首先,我们给自己写一个get_iterator_type<Container>
:
namespace adl_helper {
using std::begin; using std::end;
template<typename C>
auto adl_begin(C&& c)->decltype(begin( std::forward<C>(c) ));
template<typename C>
auto adl_end(C&& c)->decltype(end( std::forward<C>(c) ));
}
using adl_helper::adl_begin;
using adl_helper::adl_end;
template<typename... Ts> struct type_sink { typedef void type; }
template<typename... Ts> using TypeSink = typename type_sink<Ts...>::type;
template<typename Container, typename=void>
struct get_iterator_type {};
template<typename Container>
struct get_iterator_type< Container, TypeSink< adl_begin( std::declval<Container&>() ) > > {
typedef adl_begin( std::declval<Container&>() ) type;
};
template<typename Container, typename=void>
struct get_value_type {};
template<typename Container>
struct get_value_type< Container, TypeSink< std::iterator_traits< typename get_iterator_type<Container>::type > > > {
typedef std::iterator_traits< typename get_iterator_type<Container>::type > > traits;
typedef typename traits::value_type type;
};
现在,我们写下我们的to_vector
:
template<typename C>
auto to_vector( C&& container )->
std::vector<typename get_value_type<typename remove_reference<C>::type>::type>
{
std::vector<typename get_value_type<typename remove_reference<C>::type>::type> retval;
for( auto&& x : std::forward<C>(container) ) {
retval.push_back(x);
}
return retval;
}
如果我点缀所有i
并交叉所有t
,您现在既有完整的 C++11 样式迭代,又有适用于您的类型和其他可迭代容器(例如std::map
)的to_vector
。
进一步的改进可以包括检测传入的容器是否具有size
或具有随机访问迭代器,如果是,则保留retval
的大小。 但是这篇文章已经足够长了。
可以使用特征:
template<typename K> struct A { K* p; size_t n; std::string foo; typedef K my_type; };
template<typename K> struct B { K* p; size_t n; float bar[3]; typedef K my_type; };
template<typename X>
std::vector<typename X::my_type> to_vector(const X& x) {
return std::vector<typename X::my_type>(x.p, x.p+x.n);
}
A<float> a = { new float[10], 10, "hello" };
std::vector<float> v = to_vector(a);