是否可以在 C++ 中匹配递归整数模板参数?

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)

https://godbolt.org/z/AMxpuj 上的实时代码

eca*_*mur 4

您无法从 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)