【问题标题】:Recursive call to templatized function with new template arguments使用新模板参数递归调用模板函数
【发布时间】:2015-10-17 22:04:12
【问题描述】:

我正在尝试在 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

【问题讨论】:

  • 不确定,但Fold&lt;Ty, Ty&gt;::func 需要typename,因为它是依赖的。
  • 另外,return Flattener::flatten(flat); 是无限递归的(或者更确切地说,如果类型允许它编译,它将是无限递归的)。它没有拾取类的特化,因为这里的Flatten 指的是它所使用的实例化。
  • 第一个评论,我也是这么想的,但是编译没有报错,后来习惯了不写的习惯。切入正题,它仍然可以在不提及 typename 的情况下使用。
  • 第二条评论,我明白了,那么我该如何重做这个以实现我想做的事情。
  • 你可以写Flattener&lt;A, B&gt;::flatten,其中A和B是相关类型,可能是同一个类型?

标签: c++ templates visual-c++ functional-programming


【解决方案1】:

正如@tMJ 指出的那样,编写Flattener&lt;T, T&gt; 将使编译错误消失,但Flattener::flatten 方法将能够两个展平只有两个级别的深度列表(实际上当您尝试展平m-nested 时错误会回来列出m &gt;= 3 的位置,因为我们将再次出现类型不匹配)。

为了使这适用于 m 级 std::list&lt;std::list&lt;...&lt;std::list&lt;int&gt;...&gt;&gt; 的列表,我们必须有办法找出当前 Ty 容器的元素类型。例如如果Ty = std::list&lt;std::list&lt;std::list&lt;int&gt;&gt;&gt;,那么当前Ty元素的类型实际上是std::list&lt;std::list&lt;int&gt;&gt;。这就是 Ty 的类型,必须在下一步的递归中设置。

幸运的是,诸如 std::list 之类的 C++ 容器具有静态 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&lt;int&gt; 时就会发生这种情况。此时Flattener&lt;T, T&gt;::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);

P.S. 澄清一下,这里没有递归发生。每次调用Flattener&lt;T,Ty&gt;::flatten() 都会调用Flattener 类模板的不同特化的静态方法。

【讨论】:

  • 如果没有使用标准容器或任何其他没有Ty::value_type 构造的地方,他们可以使用decltype 类型说明符在编译时评估类型。请参阅@tMJ 的回答。
【解决方案2】:

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);
}

阅读更多关于递归模板中的类型推导:What are some uses of decltype(auto)?

【讨论】:

    【解决方案3】:

    你的方法对我来说似乎相当复杂。所以让我分享我的解决方案,我希望它适合你。如果您真的想要更正您的代码,而不是解决您的问题,那么这不是合适的答案。 我分享的所有代码都经过了 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::list 的value_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 }
    ...
    

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

    【讨论】:

      【解决方案4】:

      错误信息的第一件事应该是:

      没有已知的参数 1 从 'std::list' 到 'std::list>' 在静态成员函数 'static std::list Flattener::flatten(std::list) [with T = int; Ty = std::list]':

      错误的编译器?还是粘贴不好?

      这导致您希望将另一个 Flattener 称为所以更改为:

      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);
          }
      };
      

      注意额外的&lt;T,T&gt;。

      【讨论】:

      • 粘贴错误,抱歉,删除了容器的实际名称。
      • 但据我猜测,您建议的更改只会降低一层,而不是更多。
      • 那么将列表列表扁平化为列表不是您想要的吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-17
      • 2021-10-01
      • 1970-01-01
      • 2018-10-14
      • 1970-01-01
      相关资源
      最近更新 更多