c++ - 返回模板的模板

标签 c++ templates c++11

此代码不会自动正确地推断返回类型(C++ 的设计方面):

template < typename Container,
           typename UnaryOp>
Container
mymap(Container c, UnaryOp op)
{
    typedef typename Container::value_type ResultType
    Container<ResultType> result;
    for(Container::iterator i = c.begin();
        i != c.end();
        i++)
    {
        result.push_back(op(*i));
    }

    return result;
}

我想做的是让这样的事情发生:

vector<string> bar;
bar.push_back("1");
bar.push_back("2");
bar.push_back("3");    
vector<int> foomatic;
foomatic = mymap(bar, [] (string s)->int {return atoi(s.c_str());});
//foomatic now is equal to {1,2,3}

我在想 Container 会被推断为 vector,而 ResultType 会被推断为 int.

最佳答案

问题改变后:

您使用的是同一类型,Container , 用于输入和输出。但是你的输入和输出类型不同:你的输入是vector<string> ,而您的输出是 vector<int> .难怪 C++ 拒绝编译它。

你现在的问题是从输入类型中推导出返回类型。通常,C++ 不能 做到这一点。就这么简单:重载解析和模板解析仅基于输入参数发生,从不基于返回类型(在某些情况下,可以使用涉及代理对象和隐式强制转换的精心设计的技巧来解决这个问题,但我们不要去那里)。

最简单和最惯用的解决方案就是在调用函数时手动指定返回元素类型,如:

foomatic = mymap<int>(bar, [] (string s)->int {return atoi(s.c_str());});

这要求将返回元素类型放在模板参数列表的第一位:

template <
    typename ResultType,
    template<typename> class Container,
    typename InputType,
    typename UnaryOp>
Container<ResultType> mymap(Container<InputType> c, UnaryOp op) { ... }

但是,那行不通因为std::vector不符合 template<typename> class 的声明.为什么?原因很简单:因为它有不止一个模板参数。特别是,该标准表示它有至少一个额外的模板参数来指定分配器。

解决方案:将模板参数声明为 template<typename, typename> class ,对吧?

没有。现在,这确实适用于一些标准库实现。但是除了强制性的两个模板参数之外,容器可能还有其他采用默认值的模板参数(例如,这通常用于将策略类传递给容器;分配器已经是这样的策略类)。

这是一个基本问题:我们不能声明 Container以便它符合 C++ 中所有可能的容器类型签名。所以这个解决方案也是行不通的。

不幸的是,最好的解决方案更复杂,我们需要明确地重新绑定(bind)容器类型。我们可以通过一个额外的元函数来做到这一点:

template <typename C, typename T>
struct rebind;

我们需要为每个可能数量的模板参数部分特化这个元函数。例如,让它与最小的 std::vector 一起工作,我们需要以下偏特化:

template <
    template <typename, typename> class C,
    typename Old,
    typename New,
    typename A>
struct rebind<C<Old, A>, New> {
    typedef typename A::template rebind<New> Rebound;
    typedef C<New, typename Rebound::other> type;
};

这看起来令人生畏。它所做的是采用现有的 std::vector<foo>和一个类型 bar并将其重写为 std::vector<bar> .棘手的部分是我们还需要重写 allocator 类型。这是通过相当复杂的 Rebound 完成的。声明。

现在我们可以编写您的函数并调用它:

template <
    typename ResultType,
    typename C,
    typename UnaryOp>
typename rebind<C, ResultType>::type
mymap(C const& c, UnaryOp op)
{
    typename rebind<C, ResultType>::type result;
    for(typename C::const_iterator i = c.begin();
        i != c.end();
        i++)
    {
        result.push_back(op(*i));
    }

    return result;
}

int main() {
    vector<string> bar;
    bar.push_back("1");
    bar.push_back("2");
    bar.push_back("3");
    vector<int> foomatic =
        mymap<int>(bar, [] (string s)->int {return atoi(s.c_str());});
}

小菜一碟。一个非常非常复杂的蛋糕。


老问题的答案:

如果你有一个模板参数本身就是一个类模板,你需要这样声明它:

template <
    template<typename> class Container,
    typename ResultType,
    typename UnaryOp>
Container<ResultType> mymap(Container<ResultType> c, UnaryOp op) { ... }

template<typename> class Container模仿类模板声明语法并告诉编译器“Container是一个需要单个模板参数的类模板。”

但是库通常会避免这些嵌套的模板声明,而是依赖特征/元函数来传达此类信息。也就是说,通常会这样写:

template <typename Container, typename UnaryOp>
Container mymap(Container c, UnaryOp op) {
    typedef typename Container::value_type ResultType;
}

(typedef 中的 typename 是必需的,因为该名称是一个依赖 名称,而 C++ 无法识别它命名的类型。)

这个例子模仿了标准库约定的 typedef value_type在每个容器内为其关联的值类型。其他库可能遵循不同的模式。例如,我正在为一个使用外部元函数的库做贡献,其工作方式如下:

template <typename Container, typename UnaryOp>
Container mymap(Container c, UnaryOp op) {
    typedef typename Value<Container>::Type ResultType;
}

思路是一样的,唯一不同的是Container::value_type已经“外包”给了一个独立的类型。

关于c++ - 返回模板的模板,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4740042/

相关文章:

c++ - QOpenGLWidget绘制三角形

c++ - 如何用不同数量的默认参数包装一个函数,使其只有一个参数?

c++ - 为什么我不能调用派生自的模板类的模板化方法

templates - Golang 无法执行模板

c++ - 成功返回局部变量的引用

c++ - 非整数常数

c++ - 在初始化时引用另一个类

c++ - vector 类如何接受多个参数并从中创建一个数组?

c++ - CFolderPickerDialog - 无 MFC

c++ - C++ 前向声明的巨大困惑