用新的模板参数递归调用模板化函数

Recursive call to templatized function with new template arguments

本文关键字:调用 函数 递归 参数      更新时间:2023-10-16

我正在尝试在c++中实现一些功能结构。想要实现一个函数,将任意数量的列表平放

template<typename T, typename R>
struct Fold{
    typedef R(*func)(T, R);
};

template<typename T>
T head(std::list<T> const& list) {
    return list.front();
}
template<typename T>
std::list<T> tail(std::list<T> list) {
    list.pop_front();
    return list;
}
template<typename T>
std::list<T> cons(T head, std::list<T> tail){
    tail.push_front(head);
    return tail;
}
template<typename T, typename ACCUM>
ACCUM foldl(typename Fold<T, ACCUM>::func function, ACCUM accum, std::list<T> list) {
    if(list.empty())
        return accum;
    return foldl(function, (*function)(head(list), accum), tail(list));
}
template<typename T, typename ACCUM>
ACCUM foldr(typename Fold<T, ACCUM>::func function, ACCUM accum, std::list<T> list) {
    if(list.empty())
        return accum;
    return (*function)(head(list), foldr(function, accum, tail(list)));
}
template<typename T>
std::list<T> reverse(std::list<T> list){
    struct LAMBDA{
        static std::list<T> reverse(T t, std::list<T> tList){
            return cons(t, tList);
        }
    };
    std::list<T> revTList;
    return foldl( static_cast<typename Fold<T, std::list<T>>::func>(&LAMBDA::reverse), revTList, list); 
}
template<typename T>
std::list<T> append(std::list<T> list1, std::list<T> list2) {
    struct LAMBDA{
        static std::list<T> append_lambda(T t, std::list<T> list){
            return cons(t, list);;
        }
    };
    return foldl( static_cast<typename Fold<T, std::list<T>>::func>(&LAMBDA::append_lambda), list2, reverse(list1));
}
template<typename T, typename Ty>
struct Flattener{
    static std::list<T> flatten(typename std::list<Ty> deepList){
        struct LAMBDA{
            static Ty flatten_lambda(Ty ty, Ty accum){
                return append(ty, accum);
            }
        };
        Ty ty;
        Ty flat = foldr( static_cast<typename Fold<Ty, Ty>::func>(&LAMBDA::flatten_lambda), ty, deepList);
        return Flattener::flatten(flat);
    }
};
template<typename T>
struct Flattener<T, T>{
    static std::list<T> flatten(std::list<T> list){
        return list;
    }
};

上面的代码编译得很好,但是当我尝试用

调用函数时
std::list<int> emptyList;
std::list<int> list1 = cons(1, cons(2, cons(3, cons(4, emptyList))));
std::list<int> list2 = cons(5, cons(6, cons(7, cons(8, emptyList))));
std::list<std::list<int>> emptyDeepList;
std::list<std::list<int>> deepList = cons(list1, cons(list2, emptyDeepList));
Flattener<int, std::list<int>>::flatten(deepList);

我得到这个巨大的错误时,编译代码:

error C2664: 'Flattener<T,Ty>::flatten' : cannot convert parameter 1 from 'std::list<T>' to 'std::list<T>'
    with
    [
        T=int,
        Ty=std::list<int>
    ]
    and
    [
        T=int
    ]
    and
    [
        T=std::list<int>
    ]
    No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
    list.h(212) : while compiling class template member function 'std::list<T> Flattener<T,Ty>::flatten(std::list<std::list<T>>)'
    with
    [
        T=int,
        Ty=std::list<int>
    ]
    main.cpp(67) : see reference to class template instantiation 'Flattener<T,Ty>' being compiled
    with
    [
        T=int,
        Ty=std::list<int>
    ]

如果我删除对Flattener::flatten的调用,代码将编译。

我做错了什么?(由于我是c++和模板编程的新手,一些解释也会非常有帮助)。

编辑:

尝试这个。同样的错误。我想我是对的。

template<typename T, typename L>
struct Flattener{
    static std::list<T> flatten(L list){
        struct LAMBDA{
            static std::list<T> flatten_lambda(typename L1 l1, std::list<T> tList){
                return append(Flattener<T, L1>::flatten(l1), tList);
            }
        };
        std::list<T> tList;
        return foldl(&LAMBDA::flatten_lambda, tList, list);
    }
};
template<typename T>
struct Flattener<T, typename std::list<T>>{
    static std::list<T> flatten(std::list<T> list){
        return list;
    }
};

下面是这个的编译错误:

error C2664: 'Flattener<T,L>::flatten' : cannot convert parameter 1 from 'std::list<T>' to 'std::list<T>'
    with
    [
        T=int,
        L=std::list<std::list<int>>
    ]
    and
    [
        T=std::list<std::list<int>>
    ]
    and
    [
        T=std::list<int>
    ]
    No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

正如@tMJ指出的那样,编写Flattener<T, T>将使编译错误消失,但是Flattener::flatten方法将能够两个只平坦两个层次的深度列表(实际上,当您试图平坦m嵌套列表时,m >= 3会出现错误,因为我们将再次出现类型不匹配)。

为了使这个工作与m级别的列表std::list<std::list<...<std::list<int>...>>,我们必须有一种方法来找出什么是当前的Ty容器元素的类型。例如,如果Ty = std::list<std::list<std::list<int>>>,那么Ty元素的当前类型实际上是std::list<std::list<int>>。这是Ty的类型,必须在递归的下一步中设置。

幸运的是,c++容器如std::list有静态value_type属性,它是容器模板参数Type的同义词(简单来说,它将返回该容器元素的类型)。了解了这一点,我们可以用以下方法解决你的问题:

template<typename T, typename Ty>
struct Flattener {
    static std::list<T> flatten(typename std::list<Ty> deepList) {
        struct LAMBDA {
            static Ty flatten_lambda(Ty ty, Ty accum) {
                return append(ty, accum);
            }
        };
        Ty ty;
        Ty flat = foldr(static_cast<typename Fold<Ty, Ty>::func>(&LAMBDA::flatten_lambda), ty, deepList);
        return Flattener<T, Ty::value_type>::flatten(flat);
    }
};
template<typename T>
struct Flattener<T, T> {
    static std::list<T> flatten(std::list<T> list) {
        return list;
    }
};

T等于Ty::value_type时,递归将停止,当Ty等于std::list<int>时,递归将停止。此时,Flattener<T, T>::flatten()将被执行,它将产生最终结果。

我用三重嵌套的std::list测试了解决方案:

std::list<std::list<std::list<int>>> triple_empty_list;
std::list<std::list<std::list<int>>> triple_list = cons(deepList, cons(deepList, triple_empty_list));
std::list<int> result = Flattener<int, std::list<std::list<int>>>::flatten(triple_list);

注:澄清一下,这里没有发生递归。

每次调用Flattener<T,Ty>::flatten()都会调用Flattener类模板不同专门化的静态方法。

declytype救急。

template<typename T>
list<T> _flattenOneLevel(list<list<T>> listOfTList) {
    auto lambda = [](list<T> tList, list<T> accumList) {
        return reverse(tList, accumList);
    };
    return reverse(foldl(lambda, empty_list<T>(), listOfTList));
}
template<typename T, typename _DeepList>
struct _Flattener {
    static list<T> flatten(_DeepList deepList) {
        auto oneLevelDown = _flattenOneLevel(deepList);
        return _Flattener<T, decltype(oneLevelDown)>::flatten(oneLevelDown);
    }
};
template<typename T>
struct _Flattener<T, list<T>> {
    static list<T> flatten(list<T> list) {
        return list;
    }
};
template<typename T, typename _DeepList>
list<T> flatten(_DeepList deepList) {
    return _Flattener<T, _DeepList>::flatten(deepList);
}

阅读更多关于递归模板中的类型演绎:decltype(auto)的一些用途?

你的方法在我看来相当复杂。因此,让我分享我的解决方案,我希望它会适合你。如果你真的想纠正你的代码,而不是解决你的问题,这不是一个合适的答案。我分享的所有代码都经过了visual 2015的3级递归测试,但它应该适用于任何具有任何递归级别的编译器。

我们从第一个问题开始。您的解决方案需要给出数据的类型,但该类型是已知的。那么如何自动恢复呢?我们将使用一个结构体:

template <typename T>
struct NestedListType {
  using type = T;
};

给定一个类型,它只给出相同的类型。要在列表中给出类型,我们需要对它进行专门化:

template <typename T>
struct NestedListType<std::list<T> > {
  using type = typename NestedListType<T>::type;
};

现在,如果我们的类型是一个列表,它将给出std::listvalue_type,如此递归,直到该类型不是std::list。所以它可以使用多个std::list层。

它是这样使用的:

using DataType = typename NestedListType<std::list<std::list<int>>>::type; // DataType will be int

接下来我们需要定义函数。我们需要一个函数,它可以将嵌套类型的每个列表放入一个大列表中。为了避免不必要的复制,我们也将结果列表作为函数的参数传递,因此需要第一个函数来完成此操作:

// if you do not want to modify the original list, this is here where you should remove the reference and make a copy
// T could be itself an std::list, no problem with that
template <typename T>
std::list<typename NestedListType<T>::type> flatten(std::list<T> & list) {
  // obtain a std::list with the appropriate type
  std::list<typename NestedListType<T>::type> result;
  flatten(list, result);
  return result;
}

最后我们需要做实际的工作。我们需要两个函数:一个接收列表的列表并分派每个列表,另一个接收列表并将其添加到结果中。为此,我们将简单地使用重载,因为我们不能(也不需要)部分特化函数:

template <typename T, typename V>
void flatten(std::list<std::list<T> > & list, std::list<V> & result) {
  // if you want to change the order or the resulting list, change here
  // here simply add the first list first and continue until the last
  for (auto & sublist : list)
    flatten(sublist, result);
}
template <typename T, typename V>
void flatten(std::list<T> & list, std::list<V> & result) {
  // add the list to our result using references and splice to avoid unecessary copies or move
  result.splice(result.end(), list);
}

就这些!现在,您可以简单地将它用于任意数量的列表和子列表:

std::list<int> l1{ 1, 2, 3 };
std::list<int> l2{ 4, 5, 6 };
std::list<int> l3{ 7, 8, 9 };
std::list<int> l4{ 10, 11, 12 };
std::list<std::list<int> > sl1{ l1, l2 };
std::list<std::list<int> > sl2{ l3, l4 };
std::list<std::list<std::list<int> > > ssl{ sl1, sl2 };
auto result1 = flatten(l1); // gives { 1, 2, 3 }
auto result2 = flatten(sl1); // gives { 1, 2, 3, 4, 5, 6 }
auto result3 = flatten(ssl); // gives { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }
...

希望有帮助!如果有什么不清楚的地方,请告诉我,我会尽量解释得更好。

第一个错误信息应该是:

不知道参数1从'std::list'到"std:: list>' In static member functionstd::压延机列表::平(std:: list]:

车编译器吗?或者是糟糕的粘贴?

由此得出的结论是,您希望调用另一个平坦器,因此更改为:

template<typename T, typename Ty>
struct Flattener{
    static std::list<T> flatten(typename std::list<Ty> deepList){
        struct LAMBDA{
            static Ty flatten_lambda(Ty ty, Ty accum){
                return append(ty, accum);
            }
        };
        Ty ty;
        Ty flat = foldr( static_cast<typename Fold<Ty, Ty>::func>(&LAMBDA::flatten_lambda), ty, deepList);
        return Flattener<T,T>::flatten(flat);
    }
};

注意额外的<T,T>