【问题标题】:C++0x template function object inferenceC++0x 模板函数对象推断
【发布时间】:2011-04-18 02:46:05
【问题描述】:

我是一名 Scala/Java 程序员,希望将自己重新引入 C++ 并学习 C++0x 中的一些令人兴奋的特性。我想从基于 Scala 的集合设计我自己的稍微实用的集合库开始,这样我就可以对模板有一个扎实的理解。我遇到的问题是编译器似乎无法推断模板函数对象的任何类型信息。

FC++ 似乎已经使用“签名”解决了这个问题。这些看起来非常类似于 result_type 类型名,我想我会使用新的函数语法来获得它。如果可能的话,谁能建议一种在 C++0x 中做这种事情的方法,或者至少解释一下 FC++ 是如何做到这一点的?这是我正在玩的一段代码

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

template<class T>
class ArrayBuffer {
private:
    vector<T> array;
public:
    ArrayBuffer();
    ArrayBuffer(vector<T> a) : array(a) {}

    template<typename Fn>
    void foreach(Fn fn) {
        for(unsigned int i = 0; i < array.size(); i++) fn(array[i]);
    }

    template<typename Fn>
    auto map(Fn fn) -> ArrayBuffer<decltype(fn(T()))> {
        vector<decltype(fn(T()))> result(array.size());
        for(int unsigned i = 0; i < array.size(); i++) result[i] = fn(array[i]);
        return result;
    }
};

template<typename T>
class Print {
    public:
    void operator()(T elem) { cout<<elem<<endl; }
};

template<typename T>
class Square{
public:
    auto operator()(T elem) -> T {
        return elem * elem;
    }
};

int main() {
    vector<int> some_list = {5, 3, 1, 2, 4};
    ArrayBuffer<int> iterable(some_list);
    ArrayBuffer<int> squared = iterable.map(Square<int>()); // works as expected
    iterable.foreach(Print<int>()); // Prints 25 9 1 4 16 as expected
    iterable.foreach(Print()); // Is there a way or syntax for the compiler to infer that the template must be an int?
    ArrayBuffer<int> squared2 = iterable.map(Square()); // Same as above - compiler should be able to infer the template.
}

【问题讨论】:

    标签: c++ templates functional-programming function-pointers c++11


    【解决方案1】:

    您也可以将operator() 设为模板

    class Print {
        public:
        template<typename T>
        void operator()(T elem) { cout<<elem<<endl; }
    };
    

    那么你可以通过Print()。对于像ArrayBuffer&lt;decltype(fn(T()))&gt; 这样传递参数,我建议使用declval,因此您也可以使用非默认可构造T

    ArrayBuffer<decltype(fn(declval<T>()))>
    

    【讨论】:

    • 谢谢!这完美地工作。我没有想到将模板移动到操作员。我也很欣赏关于 declval 的提示。我确实有一个简短的跟进 - 有没有办法使用模板函数指针来做到这一点?例如,如果我们有一个模板打印函数,我可以调用 iterable.foreach(&print);我相信答案是否定的。
    【解决方案2】:

    您似乎在重新发明 C++ 标准库。我不是在谈论你的容器。
    C++ 的功能已经很完善了。

    我认为您遗漏了有关 C++ 标准库的一些关键点。

    • 首先是通用,其次是面向对象
      如果一个算法可以以通用的方式实现,它将不会包含在类中。
      ArrayBuffer::foreach == std::for_each
      ArrayBuffer::map == std::transform
    • 标准算法适用于迭代器而不是完整的容器。新的 C++ 程序员经常忽略这一点,因为 Java 和 C# 都缺乏这个概念。迭代器比单独的容器更具表现力/灵活性。迭代器是arguably 要走的路。也就是说,Ranges 是表达迭代器的更简洁的方式(范围只是成对的迭代器)。

    这是一个使用 C++ 函数式的示例。这也是为什么 C# 不使用迭代器的一个很好的例子。尽管它们非常强大,但它们的冗长却让 C# 的目标受众望而生畏。 Java 不使用迭代器,因为它们不是面向对象的,而且语言设计者一开始就对此非常不满。

    struct Print
    {
       template<typename T>
       void operator()( const T& t )
       { std::cout << t << std::endl; }
    };
    
    struct Squared
    {
       template<typename T>
       T operator()( const T& t )
       { return t*t; }
    };
    
    int main()
    {
       std::vector<int> vi;
       std::foreach( vi.begin(), vi.end(), Print());
       std::foreach( vi.begin(), vi.end(), [](int i){ std::cout<<i<<std::endl; } );
    
       std::vector<int> vi_squared;
    
       std::transform( vi.begin(), vi.end(), std::back_inserter(vi_squared), Squared() );
       // or
       vi_squared.resize( vi.size() );
       std::transform( vi.begin(), vi.end(), vi_squared.begin(), Squared() );
    }
    

    【讨论】:

    • 感谢您的提示。我了解 STL 如何以一种很好的方式将一些算法与容器分开。它仍然遗漏了许多东西,例如折叠、减少、视图、切片、扁平化、flatMaps 等。最后,C++ 中的变换是破坏性的。除了可变集合之外,我还想实现完全持久的集合,而这不能通过 STL 的拆分设计来完成。地图应该是对容器的操作,容器应该能够选择如何实现。
    • 实际上,我收回了这一点。变换似乎没有破坏性。尽管如此,我认为将操作作为容器的一部分是有意义的,并且可以让每个容器实现自己的特定行为。我不确定我能从这个设计中得到多少代码重用,因为我的算法将与迭代混合。我还希望使用 CRTP 将我在一个层次结构下实现的任何不同集合结合起来,以便我可以呈现一个接口,而不是来自库的实现(模板方法不能是虚拟的)。范围看起来很棒 - 我很欣赏这个链接。
    • 当容器行为用于std::transform 或任何标准算法时,您仍然可以对其进行特化。您没有继承和覆盖 map 方法,而是为您的容器专门化(技术上是部分专门化)std::transform 方法。通常,定义如何遍历集合就足够了,迭代器做得很好(范围/视图做得更好)。这对我来说是正确的。 99% 的时间地图算法不关心存储语义。但如果你真的需要进入那里做一些疯狂的事情,你可以专攻。
    【解决方案3】:

    编译器有没有办法或语法来推断模板必须是一个int?

    问题是模板不需要 是 int。
    这同样有效。

    iterable.foreach(Print<float>());
    

    你真正要问的是。
    我可以根据使用的上下文使模板仿函数具有不同的默认值吗?

    答案是否定的。我想我们理论上可以在简单的情况下做到这一点。然而,极端情况很快使这不可行。函数的真正上下文是完整的编译单元,而不仅仅是实例化它的那一行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-02
      • 1970-01-01
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多