bra*_*ing 8 c++ templates template-meta-programming c++11
我有以下问题。我定义了一个 N 维向量
#include <vector>
#include <utility>
#include <string>
template <int N, typename T>
struct NVector{
typedef std::vector<typename NVector<N-1,T>::type> type;
};
template <typename T> struct NVector<1,T> {
typedef std::vector<T> type;
};
Run Code Online (Sandbox Code Playgroud)
我希望编写一个高阶函数Map,它可以转换嵌套向量的叶元素,无论多深,并返回一个新的相同形状的嵌套向量。我试过了
template <int N, typename T, typename Mapper>
struct MapResult {
typedef decltype ( (std::declval<Mapper>()) (std::declval<T>()) ) basic_type;
typedef typename NVector<N, basic_type>::type vector_type;
};
template <int N, typename T, typename Mapper>
typename MapResult<N,T,Mapper>::vector_type
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
{
typename MapResult<N,T,Mapper>::vector_type out;
for(auto i = vector.begin(); i != vector.end(); i++){
out.push_back(Map(*i,mapper));
}
return out;
}
template <typename T, typename Mapper>
typename MapResult<1,T,Mapper>::vector_type
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
{
typename MapResult<1,T,Mapper>::vector_type out;
for(auto i = vector.begin(); i != vector.end(); i++){
out.push_back(mapper(*i));
}
return out;
}
Run Code Online (Sandbox Code Playgroud)
然后在 main 中使用它
int main(){
NVector<1,int>::type a = {1,2,3,4};
NVector<2,int>::type b = {{1,2},{3,4}};
NVector<1,std::string>::type as = Map(a,[](int x){return std::to_string(x);});
NVector<2,std::string>::type bs = Map(b,[](int x){return std::to_string(x);});
}
Run Code Online (Sandbox Code Playgroud)
但是我得到编译错误
<source>:48:34: error: no matching function for call to 'Map'
NVector<1,double>::type da = Map(a,[](int x)->int{return (double)x;});
^~~
<source>:20:5: note: candidate template ignored: couldn't infer template argument 'N'
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
^
<source>:31:5: note: candidate template ignored: couldn't infer template argument 'T'
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
^
<source>:49:34: error: no matching function for call to 'Map'
NVector<2,double>::type db = Map(b,[](int x)->int{return (double)x;});
^~~
<source>:20:5: note: candidate template ignored: couldn't infer template argument 'N'
Map( typename NVector<N,T>::type const & vector, Mapper mapper)
^
<source>:31:5: note: candidate template ignored: couldn't infer template argument 'T'
Map(typename NVector<1,T>::type const & vector, Mapper mapper)
^
2 errors generated.
Compiler returned: 1
Run Code Online (Sandbox Code Playgroud)
我猜编译器不够聪明(或者标准没有指定如何)通过推导来计算参数 N。有没有办法实现这一目标?
我以前有过这个工作,但实际上是从 std::vector 派生出来的,但我不喜欢这个解决方案,因为让它与当前现有的代码一起工作会很好,而不必引入新的包装器类型。
/// define recursive case
template <int N, typename T>
struct NVector : std::vector<NVector<N-1,T>>;
/// define termination case
template <typename T>
struct NVector<1, T> : public std::vector<T>;
Run Code Online (Sandbox Code Playgroud)
您无法从 typedef 中进行推断 - 尤其是在辅助类中声明的 typedef - 因为编译器无法执行从类型到参数组合的反向映射。
(考虑到在一般情况下这是不可能的,因为有人可能会专门化struct NVector<100, float> { using type = std::vector<char>; };
,并且编译器无法知道这是否是有意的。)
为了帮助编译器,您可以定义反向映射:
template<class T> struct NVT { static constexpr auto D = 0; using V = T; };
template<class T> struct NVT<std::vector<T>> : NVT<T> {
static constexpr auto D = NVT<T>::D + 1;
};
Run Code Online (Sandbox Code Playgroud)
可能的用法(C++17,但很容易翻译成古老的方言):
template<class NV, class Mapper>
auto Map(NV const& vector, Mapper mapper) {
static constexpr auto N = NVT<NV>::D;
using T = typename NVT<NV>::V;
if constexpr (N == 0)
return mapper(vector);
else
{
typename MapResult<N,T,Mapper>::vector_type out;
for (auto const& x : vector)
out.push_back(Map(x, mapper));
return out;
}
}
Run Code Online (Sandbox Code Playgroud)