嵌套模板函数的重载

Overload of a nested template function

我想了很久我的问题应该取什么标题,但还是失败了,所以如果你找到一个好的,请编辑它。

我正在尝试为 vector 或其他 container<T> 编写一个打印函数,并为 container<container<T>> 编写另一个打印函数,所以我想到了:

template<typename T>
void print(T const& cont){
    for (const auto& i : cont) {
        cout << i << " ";
    }
    cout << endl;
}

template<typename T, template<typename> typename Cont>
void print(Cont<T> const& cont) {
    for (const auto& i : cont) {
        print(i);
    }
}

这里有我的 2 个目标容器:

vector<vector<int>> subsets;
vector<int> subset;

当我调用 print(subset); 时,程序按预期运行,但是当我调用 print(subsets) 时,编译器开始报错:

error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const std::vector<int,std::allocator<int>>' (or there is no acceptable conversion)

我的结论是它仍在尝试调用 non-nested 模板打印函数并在 cout 上失败,因为我正在尝试计算向量。

任何人都可以解释为什么重载解析没有像我预期的那样工作以及我在这里做错了什么?即使我将 nested-template 函数重命名为 printn,它也会因为不同的原因开始抱怨:

error C2784: 'void prints(const Cont<T> &)': could not deduce template argument for 'const Cont<T> &' from 'std::vector<std::vector<int,std::allocator<int>>,std::allocator<std::vector<int,std::allocator<int>>>>'

简短、简单但不充分的答案是 std::vector 有 2 个模板参数。您还应该包括一些间距:

template<class T, class A, template<class, class>class C>
void print(C<T,A> const& cont) {
  std::cout << "[ ";
  bool bFirst = true;
  for (const auto& i : cont) {
    if (!bFirst)
      std::cout << ", ";
    bFirst = false;
    print(i);
  }
  std::cout << " ]";
}

因此从未调用过重载。

执行此操作后,您的代码将无法运行,因为您没有元素打印机。所以用元素打印机替换你的其他循环打印机:

template<typename T>
void print(T const& i){
  std::cout << i;
}

Live example.

测试代码:

std::vector<int> a={1,2,3};
print(a);
std::cout << "\n";
std::vector<std::vector<int>> b = {a, a, a};
print(b);
std::cout << "\n";

输出:

[ 1, 2, 3 ]
[ [ 1, 2, 3 ], [ 1, 2, 3 ], [ 1, 2, 3 ] ]

这是不够的,因为如果您想要更严肃的通用打印机,您真的应该做一些更有趣的事情来检测 "is this object iterable" 和 "is this object tuple-like"。检测 Cont<A,B> 模板是一个糟糕的替代品。

Here 是检测某物是否可迭代的代码(忽略错误的检查答案,阅读我链接到的那个)。

然后在 print 中对执行 for(:) 循环的 "is the argument iterable" 进行 SFINAE 测试。

接下来您要做的是检测对象是否类似于元组。如果是,您想要打印出元组的每个元素。这给你 std::mapstd::unordered_map 支持。请注意,std::array 既像元组又可迭代。

这比检测 "iterable" 更难一些,并且随着您使用的 C++ 标准的不同而有所不同,因为新版本的 C++ 正在扩展类似元组的特性。你可以偷懒,只检测 std::pairstd::tuple,这将覆盖 99% 的用例。

您还可以使用 SFINAE(或 C++ 概念,如果您生活在未来)来获得您想要的结果,而不必知道您的传入容器有多少模板参数。这是一个使用尾随 return 类型来执行 SFINAE 的示例:

#include <iostream>
#include <vector>

template<typename T>
auto print( const T& cont ) -> decltype(std::cout << *begin(cont), void()) 
{
    for( auto&& i : cont )
        std::cout << i << ' ';
    std::cout << '\n';
}

template<typename T>
auto print( const T& cont ) -> decltype(begin(*begin(cont)), void())
{
    for( auto&& i : cont )
        print(i);
}

int main()
{
    const auto subset1  = std::vector<int>{ 1, 2, 3, 4 };
    const auto subset2 = std::vector<std::vector<int>>{ {5,6,7}, {8,9} };
    const auto subset3 = std::vector<std::vector<std::vector<int>>>{ 
        { {10,20,30}, {40,50} }, 
        { {60}, {70,80,90}, {100,110,120} },
        { {200,400,600} }
    };

    print( subset1 );
    print( subset2 );
    print( subset3 );
}

Coliru 上看到它输出:

1 2 3 4 
5 6 7 
8 9 
10 20 30 
40 50 
60 
70 80 90 
100 110 120 
200 400 600 

请注意第一个函数是如何表达它需要通过 begin() 函数(由 range-for 循环隐式使用)编写单个元素的能力,而第二个函数至少需要通过 begin()

我自己可能会倾向于使用可变参数模板,除非您需要将容器与容器的容器(容器的(容器的...)分开处理)。

您的问题是 std::vector 有不止一种模板类型。因此,改用 T 重载。发生这种情况的原因是语言中关于如何在模板模板参数中考虑默认模板参数的歧义。这导致了缺陷报告 DR150 which was adopted in C++17 and will allow your code to work in a compliant compiler1。要解决此问题,您可以使用可变参数模板模板参数并调整基本情况以仅打印

之类的元素
template<typename T>
void print(T const& elem){
    cout << elem << " ";
}

template<template<typename...> typename Cont, typename... Params>
void print(Cont<Params...> const& cont) {
    for (const auto& i : cont) {
        print(i);
    }
    cout << endl;
}

int main()
{
    vector<vector<int>> subsets{{1,2},{3,4}};
    print(subsets);
}

产出

1 2 
3 4 

1:我必须像我的示例中那样调整基本情况,因为现在模板模板版本将调用嵌套向量