返回模板的模板

发布于 2024-10-13 01:01:59 字数 793 浏览 2 评论 0原文

此代码不会自动正确推断返回类型(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 将被推断为 vectorResultType 将被推断为 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

暮年 2024-10-20 01:01:59

问题改变后:

您使用相同的类型Container进行输入和输出。但您的输入和输出类型是不同的:您的输入是矢量,而输出是矢量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的声明。类。为什么?原因很简单:因为它不止一个模板参数。特别是,该标准规定它至少有一个额外的模板参数来指定分配器。

解决方案:将模板参数声明为 template;类,对吗?

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

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

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

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 和类型 bar 并将其重写为 std::vector >。棘手的部分是我们还需要重写分配器类型。这是通过相当复杂的 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) { ... }

templateclass Container 模仿类模板声明语法,并告诉编译器“Container 是一个需要单个模板参数的类模板”。

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

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

(typedef 中的 typename 是必要的,因为该名称是一个依赖的名称,而 C++ 无法确定它是否命名为type。)

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

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

想法是相同的,唯一的区别是 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 is vector<string>, whereas your output is vector<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:

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

This requires that the return element type be put first in the template argument list:

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

However, that does not work because std::vector does not fit the declaration of template<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:

template <typename C, typename T>
struct rebind;

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:

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

This looks daunting. What it does is take an existing std::vector<foo> and a type bar and rewrite it to a std::vector<bar>. The tricky part is that we also need to rewrite the allocator type. This is done via the rather complicated Rebound declaration.

Now we can write your function, and invoke it:

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

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:

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

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:

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

(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:

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

The idea is the same, the only difference is that Container::value_type has been “outsourced” to an independent type.

烟若柳尘 2024-10-20 01:01:59

你需要一些类似的东西:

template<typename Container, typename UnaryOp>
auto mymap(Container c, UnaryOp op) -> Container::rebind<decltype(op(*c.begin()))>
{
    typedef typename Container::value_type InputType;
    typedef decltype( op( InputType() ) ) ResultType;
    typedef typename Container::rebind<ResultType> ResultContainer;

    // ...
}

You need something along the lines of:

template<typename Container, typename UnaryOp>
auto mymap(Container c, UnaryOp op) -> Container::rebind<decltype(op(*c.begin()))>
{
    typedef typename Container::value_type InputType;
    typedef decltype( op( InputType() ) ) ResultType;
    typedef typename Container::rebind<ResultType> ResultContainer;

    // ...
}
带刺的爱情 2024-10-20 01:01:59

您可以使用称为 auto_cast 的技巧,我们将稍微重写该技巧以针对容器。

template<typename container> struct auto_cast_container {
    container c;
    template<typename out_type> operator out_type() {
        return out_type(c.begin(), c.end());
    }
};

template<typename Container, typename UnaryOperator>
auto 
mymap(const Container& c, UnaryOperator op)
-> auto_cast_container<std::vector<decltype(op(*c.begin()))>> {
    std::vector<decltype(op(*c.begin()))> retval;
    std::for_each(c.begin(), c.end(), [&](decltype(*c.begin())& ref) {
        retval.push_back(op(ref));
    });
    auto_cast_container<std::vector<decltype(op(*c.begin()))>> return_value;
    return_value.c = std::move(retval);
    return return_value;
}

实际上,模板化转换运算符允许转换为任何接受开始/结束构造函数的类型。这意味着如果您愿意,您可以从向量映射到列表,并且如果您需要的话,它还可以将对映射到关联容器并再次映射回来。如果您追求效率,可以进一步调整,但为了清楚起见,我将其省略。

编辑:康拉德的评论指出了一些逻辑缺陷。我还通过在所有适当的情况下使用 decltype 提高了系统的安全性和透明度。

You can use a trick known as auto_cast, which we will rewrite a little bit to be specific to containers.

template<typename container> struct auto_cast_container {
    container c;
    template<typename out_type> operator out_type() {
        return out_type(c.begin(), c.end());
    }
};

template<typename Container, typename UnaryOperator>
auto 
mymap(const Container& c, UnaryOperator op)
-> auto_cast_container<std::vector<decltype(op(*c.begin()))>> {
    std::vector<decltype(op(*c.begin()))> retval;
    std::for_each(c.begin(), c.end(), [&](decltype(*c.begin())& ref) {
        retval.push_back(op(ref));
    });
    auto_cast_container<std::vector<decltype(op(*c.begin()))>> return_value;
    return_value.c = std::move(retval);
    return return_value;
}

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文