返回模板的模板
此代码不会自动正确推断返回类型(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
。
This code does not automatically infer the return type correctly (a design aspect of 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;
}
What I would like to do is have something like this happen:
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}
I was figuring that Container
would be inferred to be vector
, and ResultType
would be inferred to be int
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
问题改变后:
您使用相同的类型,而输出是矢量
Container
进行输入和输出。但您的输入和输出类型是不同的:您的输入是矢量int
。难怪 C++ 拒绝编译这个。您现在的问题是从输入类型推断返回类型。一般来说,C++不能做到这一点。就这么简单:重载解析和模板解析仅基于输入参数发生,而不是基于返回类型(在某些情况下,可以使用涉及代理对象和隐式强制转换的复杂技巧来解决此问题,但我们不去那里)。
最简单、最惯用的解决方案就是在调用函数时手动指定返回元素类型,如下所示:
这要求将返回元素类型放在模板参数列表的第一位:
但是,这不起作用 因为
std::vector
不符合template的声明。类。为什么?原因很简单:因为它不止一个模板参数。特别是,该标准规定它至少有一个额外的模板参数来指定分配器。
解决方案:将模板参数声明为
template;类
,对吗?不。现在,这确实适用于某些标准库实现。但除了强制的两个模板参数之外,容器还可能具有采用默认值的其他模板参数(例如,这通常用于将策略类传递给容器;分配器已经是这样的策略类)。
这是一个根本问题:我们无法声明
Container
,使其符合 C++ 中容器的所有可能的类型签名。所以这个解决方案也是行不通的。不幸的是,最好的解决方案更复杂,我们需要显式地重新绑定容器类型。我们可以通过一个额外的元函数来做到这一点:
我们需要为每个可能数量的模板参数部分专门化这个元函数。例如,为了使其与最小的
std::vector
一起工作,我们需要以下部分专业化:这看起来令人畏惧。它的作用是获取现有的
std::vector
和类型bar
并将其重写为std::vector
>。棘手的部分是我们还需要重写分配器类型。这是通过相当复杂的Rebound
声明来完成的。现在我们可以编写您的函数并调用它:
小菜一碟。一个非常非常复杂的蛋糕。
老问题的答案:
如果您有一个模板参数本身就是一个类模板,则需要这样声明它:
templateclass Container
模仿类模板声明语法,并告诉编译器“Container 是一个需要单个模板参数的类模板”。但库通常会避免这些嵌套模板声明,而是依赖特征/元函数来传达此类信息。也就是说,它通常会写成如下:
(typedef 中的
typename
是必要的,因为该名称是一个依赖的名称,而 C++ 无法确定它是否命名为type。)此示例模仿标准库约定,即在每个容器内为其关联的值类型设置 typedef
value_type
。其他库可能遵循不同的模式。例如,我正在为一个使用外部元函数的库做出贡献,其工作原理如下:想法是相同的,唯一的区别是
Container::value_type
已“外包”到独立类型。After the question changed:
You are using the same type,
Container
, for input and output. But your input and output types are distinct: your input isvector<string>
, whereas your output isvector<int>
. No wonder that C++ refuses to compile this.Your problem is now to deduce the return type from the input types. Generally, C++ cannot do that. It’s as simple as that: overload resolution and template resolution only happens based on the input arguments, never on the return type (in some cases elaborate tricks involving proxy objects and implicit casts can be used to work around this but let’s not go there).
The simplest and most idiomatic solution is just to specify the return element type manually when calling the function, as in:
This requires that the return element type be put first in the template argument list:
However, that does not work because
std::vector
does not fit the declaration oftemplate<typename> class
. Why? Simple reason: because it has more than just one template argument. In particular, the standard says that it has at least one extra template argument to specify the allocator.Solution: declare the template argument as
template<typename, typename> class
, right?No. Now, this does work for some standard library implementations. But besides the mandated two template arguments, the containers may have additional template arguments that take default values (this is often used to pass policy classes to a container, for example; the allocator is already such a policy class).
This is a fundamental problem: we cannot declare
Container
so that it conforms to all possible type signatures of containers in C++. So this solution, too, is a no-go.The best solution is unfortunately more complicated, we need to rebind the container type explicitly. This we can do via an extra metafunction:
We need to partially specialize this metafunction for each possible number of template parameters. For example, to make it work with the minimal
std::vector
, we’d need the following partial specialization:This looks daunting. What it does is take an existing
std::vector<foo>
and a typebar
and rewrite it to astd::vector<bar>
. The tricky part is that we also need to rewrite the allocator type. This is done via the rather complicatedRebound
declaration.Now we can write your function, and invoke it:
Piece of cake. A really, really, complicated cake.
Answer to old question:
If you have a template parameter that is itself a class template, you need to declare it as such:
The
template<typename> class Container
mimics the class template declaration syntax and tells the compiler that “Container
is a class template that expects a single template argument.”But libraries usually avoid these nested template declarations and instead rely on traits/metafunctions to communicate such information. That is, it would usually be written as follows:
(The
typename
in the typedef is necessary because the name is a dependent name and C++ cannot figure out that this it names a type.)This example mimics the standard library convention of having a typedef
value_type
inside each container for its associated value type. Other libraries may follow different schemas. For example, I am contributing to a library that uses external metafunctions that work as follows:The idea is the same, the only difference is that
Container::value_type
has been “outsourced” to an independent type.你需要一些类似的东西:
You need something along the lines of:
您可以使用称为 auto_cast 的技巧,我们将稍微重写该技巧以针对容器。
实际上,模板化转换运算符允许转换为任何接受开始/结束构造函数的类型。这意味着如果您愿意,您可以从向量映射到列表,并且如果您需要的话,它还可以将对映射到关联容器并再次映射回来。如果您追求效率,可以进一步调整,但为了清楚起见,我将其省略。
编辑:康拉德的评论指出了一些逻辑缺陷。我还通过在所有适当的情况下使用 decltype 提高了系统的安全性和透明度。
You can use a trick known as auto_cast, which we will rewrite a little bit to be specific to containers.
Effectively, the templated conversion operator allows for conversion to any type which will accept the begin/end constructor. This means that you can map from a vector to a list, if you like, and it can also map pairs into associative containers and back again, should you need it. If you're hunting for efficiency this can be tuned further but I left that out for clarity.
Edit: Konrad's comment pointed out a couple of logical flaws. I also improved the safety and transparency of the system by using decltype in all appropriate cases.