模板模板参数 - 为什么在这种情况下需要它们?

时间:2018-03-29 09:50:27

标签: c++

我试图弄清楚为什么下面的一段代码,其中编写了流输出操作符函数的模板,需要使用模板模板参数:

https://wandbox.org/permlink/W85pV5GhVzI95b3e

#include <iostream>
#include <vector>
#include <deque>
#include <list>

template<template <class> class C, class T>
std::ostream& operator <<(std::ostream& os, const C<T>& objs)
{
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

int main()
{
    std::vector<float> vf { 1.1, 2.2, 3.3, 4.4 };
    std::cout << vf << '\n';

    std::list<char> lc { 'a', 'b', 'c', 'd' };
    std::cout << lc << '\n';

    std::deque<int> di { 1, 2, 3, 4 };
    std::cout << di << '\n';

    return 0;
}

为什么我不能像这样编写运算符函数的模板:

template <class T>
std::ostream& operator <<(std::ostream& os, T& objs) {...}

在这种情况下,我收到很多错误: 错误:&#39;运算符&lt;&lt;&lt;&# (操作数类型是&#39; std :: ostream&#39; {aka&#39; std :: basic_ostream&#39;}和&#39; const char&#39;)              os&lt;&lt; obj&lt;&lt; &#39; &#39 ;;

有人能帮我理解吗?

3 个答案:

答案 0 :(得分:3)

虽然我同意 Jarod42 您不需要模板模板参数,但请允许我为您的特定情况演示更简单的解决方案:

template<class T>
auto operator <<(std::ostream& os, T const& objs)
  -> decltype(std::begin(objs), std::end(objs), (os))
{
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

某些表达式SFINAE的尾随返回类型,一切都按您的意愿运行。 SFINAE部分发生在decltype()返回类型中。逗号运算符使其std::begin(objs)std::end(objs)然后(os)被检查为格式良好。如果其中任何一个格式不正确,则不考虑该函数的重载解析。但是因为逗号运算符的类型与其最后一个操作数的类型相同,所以由于std::ostream&扣除规则,我们从(os)中得到decltype

为什么std::beginstd::end?事实恰恰相反,它们适用于几乎所有可以输入到基于循环的范围的类型。因此,检查将涵盖任何遵循基于循环的范围的迭代协议的类型。

Here it is, live

答案 1 :(得分:2)

不需要模板模板参数。

代码的作者假定可迭代对象的格式为C<T>,这是错误的:

  • 存在C<T>不可迭代,如std::unique_ptr<T> ...
  • std::vector有更多参数(默认),因此在C++2a之前,它不会匹配C<T>

使用特征(如that question)会更好:

template<class T, std::enable_if_t<is_iterable<T>::value>* = nullptr>
std::ostream& operator <<(std::ostream& os, const T& objs)
{
    for (auto const& obj : objs)
        os << obj << ' ';
    return os;
}

答案 2 :(得分:0)

您无法像这样编写模板:

template <class T>
std::ostream& operator <<(std::ostream& os, T& objs) {...}

因为这会为每个(!)对象重载ostream。 T可以是任何东西,而不仅仅是列表。

我认为处理此问题的最佳方法是为您想要的每种类型的列表指定重载。 E.g:

template <class T>
std::ostream& operator <<(std::ostream& os, std::vector<T>& objs) {...}

template <class T>
std::ostream& operator <<(std::ostream& os, std::list<T>& objs) {...}

依旧......