返回模板模板

Pau*_*han 1 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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}
Run Code Online (Sandbox Code Playgroud)

我认为这Container将被推断为vector,并且ResultType将被推断为int.

Kon*_*lph 8

问题改变后:

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

您现在的问题是从输入类型中推断出返回类型.通常,C++ 不能这样做.它就是这么简单:重载决策和模板解析只发生在输入参数上,永远不会在返回类型上发生(在某些情况下,涉及代理对象的精巧技巧和隐式强制转换可以用来解决这个问题,但不要去那里).

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

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

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

template <
    typename ResultType,
    template<typename> class Container,
    typename InputType,
    typename UnaryOp>
Container<ResultType> mymap(Container<InputType> c, UnaryOp op) { ... }
Run Code Online (Sandbox Code Playgroud)

但是,这不起作用,因为std::vector不符合声明template<typename> class.为什么?原因很简单:因为它不仅仅有一个模板参数.特别是,标准说它至少有一个额外的模板参数来指定分配器.

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

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

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

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

template <typename C, typename T>
struct rebind;
Run Code Online (Sandbox Code Playgroud)

我们需要为每个可能数量的模板参数部分地专门化这个元函数.例如,为了使它与minimal一起工作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;
};
Run Code Online (Sandbox Code Playgroud)

这看起来令人生畏.它的作用是将一个现有的std::vector<foo>和一个类型bar重写为一个std::vector<bar>.棘手的部分是我们还需要重写分配器类型.这是通过相当复杂的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());});
}
Run Code Online (Sandbox Code Playgroud)

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


回答旧问题:

如果您有一个模板参数本身就是一个类模板,您需要将其声明为:

template <
    template<typename> class Container,
    typename ResultType,
    typename UnaryOp>
Container<ResultType> mymap(Container<ResultType> c, UnaryOp op) { ... }
Run Code Online (Sandbox Code Playgroud)

template<typename> class Container模拟类模板声明语法和告诉编译器" Container是只需要一个模板参数类模板."

但是库通常会避免使用这些嵌套模板声明,而是依赖traits/metafunctions来传递这些信息.也就是说,它通常写成如下:

template <typename Container, typename UnaryOp>
Container mymap(Container c, UnaryOp op) {
    typedef typename Container::value_type ResultType;
}
Run Code Online (Sandbox Code Playgroud)

(typenametypedef中的名称是必需的,因为名称是依赖名称,C++无法确定它是否为类型命名.)

此示例模仿标准库约定,即value_type在每个容器中为其关联的值类型设置typedef .其他库可能遵循不同的模式.例如,我正在为使用外部元函数的库做出贡献,其工作方式如下:

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

这个想法是一样的,唯一的区别是Container::value_type已经"外包"到一个独立的类型.