【问题标题】:What are std::vector deduction guides in C++17?什么是 C++17 中的 std::vector 推导指南?
【发布时间】:2018-02-10 06:18:28
【问题描述】:

我通过使用 cppreference 阅读了有关 std::vector 的演绎指南。

示例:

#include <vector>

int main() {
   std::vector<int> v = {1, 2, 3, 4};
   std::vector x{v.begin(), v.end()}; // uses explicit deduction guide
}

所以,我对此有一些疑问:

  • 什么是 C++17 中的 std::vector 推导指南?

  • 为什么以及何时需要向量推导?

  • 这里,x 是 std::vector&lt;int&gt; 还是 std::vector&lt;std::vector&lt;int&gt;&gt;?

【问题讨论】:

  • x 可能是std::vector&lt;std::vector&lt;int&gt;::iterator&gt;。
  • 尝试将值添加到 x 以查看:Demo 所以std::vector&lt;std::vector&lt;int&gt;::iterator&gt;。
  • 你知道一般的扣分指南是什么吗?如果没有,this 可能会有用。
  • @gsamaras:我的意思是通过测试x.push_back(42);、x.push_back(v.begin()); 和x.push_back(v);(正确的)编译器(带有正确的库)会告诉错误/有效情况(而且错误情况应该可能给出正确的类型)。

标签: c++ templates vector c++17 template-argument-deduction


【解决方案1】:

什么是 C++17 中的std::vector 推导指南?

用户定义的推导指南允许用户决定class template argument deduction 如何从模板类的构造函数参数中推导出参数。在这种情况下,std::vector 似乎有一个明确的指南,应该使迭代器对的构造更加直观。


为什么以及何时需要向量推导?

我们并不“需要”它,但它在通用代码和非常明显的代码中很有用(即显式指定模板参数对读者不利的代码)。


x 是 vector&lt;int&gt; 还是 vector&lt;vector&lt;int&gt;&gt;?

这是一个快速解决这个问题的好方法 - 编写一个没有定义的模板函数声明并尝试调用它。编译器将打印出传递参数的类型。这是 g++ 8 打印出来的内容:

template <typename> 
void foo();

// ...

foo(x);

错误:没有匹配的函数调用foo(std::vector&lt;__gnu_cxx::__normal_iterator&lt;int*, std::vector&lt;int&gt; &gt; ...

从错误信息中可以看出,x 推导出为std::vector&lt;std::vector&lt;int&gt;::iterator&gt;。


为什么?

std::vector的推演指南are available on cppreference.org。该标准似乎从迭代器对中定义了一个明确的推导指南:

无论如何,在 g++ 8 中遇到的行为似乎都是正确的,因为 (quoting Rakete1111)

  • 重载解析更喜欢带有 std::initializer_list 和花括号初始化列表的构造函数

  • 只有在 list-initialization 中尝试了所有 std::initializer_list 构造函数后,才会考虑其他构造函数

std:vector&lt;std::vector&lt;int&gt;::iterator&gt; 因此是使用列表初始化时的正确结果。 live example

当用std::vector x(v.begin(), v.end()) 构造x 时,将改为推导int。 live example

【讨论】:

  • 你的最后一点只对了一半。即使缺少推导指南,类型也将是 std:vector&lt;std::vector&lt;int&gt;::iterator&gt;,因为重载解析更喜欢带有大括号初始化列表的 std::initializer_list 的构造函数。
  • @Rakete1111 这不是 cppreference 上的示例所暗示的 - 我不知道示例是否正确,但在某些情况下,用户定义的扣除指南优先于自动扣除指南,这可能会使这里的推论正确。
  • @songyuanyao 是的,我也这么认为,但不是只有在list-initialization 尝试了所有std::initializer_list 构造函数之后才考虑其他构造函数?因此,我认为甚至不考虑迭代器对构造函数。
  • @Rakete1111 所说的。 std:vector&lt;std::vector&lt;int&gt;::iterator&gt; 是正确的结果。
  • 这个 cppreference 示例现在已修复。
【解决方案2】:

这里,x 是 std::vector&lt;int&gt; 还是 std::vector&lt;std::vector&lt;int&gt;&gt;?

这里的其他答案解决了您的其他问题,但我想更彻底地解决这个问题。当我们进行类模板参数推导时,我们从构造函数中synthesize a bunch of function templates,然后从deduction guides 中进行更多操作,并执行重载决策以确定正确的模板参数。

std::vector&lt;T,A&gt; 有很多构造函数,但其​​中大多数都没有提到 T,这会使 T 成为非推导上下文,因此在此重载中不是一个可行的选择。如果我们预先修剪该集合以仅使用可能可行的集合:

template <class T> vector<T> __f(size_t, T const& );    // #2
template <class T> vector<T> __f(vector<T> const& );    // #5
template <class T> vector<T> __f(vector<T>&& );         // #6, NB this is an rvalue ref
template <class T> vector<T> __f(initializer_list<T> ); // #8

还有这个deduction guide,我也会通过删除分配器来简化它:

template <class InputIt>
vector<typename std::iterator_traits<InputIt>::value_type> __f(InputIt, InputIt );

这些是我们的 5 名候选人,我们正在超载,就像 [dcl.init] 一样,通过__f({v.begin(), v.end()}) 调用。因为这是列表初始化,所以我们 start with the initializer_list candidates 并且只有在没有任何候选者的情况下,我们才会继续处理其他候选人。在这种情况下,有一个initializer_list 候选者是可行的(#8),所以我们选择它而不考虑其他任何一个。该候选将T 推导出为std::vector&lt;int&gt;::iterator,因此我们然后重新启动重载解析过程以选择具有两个迭代器的列表初始化std::vector&lt;std::vector&lt;int&gt;::iterator&gt; 的构造函数。

这可能不是我们想要的结果——我们可能想要一个vector&lt;int&gt;。解决方案很简单:使用()s:

std::vector x(v.begin(), v.end()); // uses explicit deduction guide

现在,我们不进行列表初始化,因此 initializer_list 候选者不是可行的候选者。结果,我们通过推导指南(唯一可行的候选者)推导vector&lt;int&gt;,并最终调用它的迭代器对构造函数。这具有实际使评论正确的副作用。


这是使用{} 初始化与使用() 初始化完全不同的许多地方之一。有人认为{} 是统一初始化——这样的例子似乎可以反驳。我的经验法则:当你特别有意识地需要{} 提供的行为时,使用{}。 () 否则。

【讨论】:

    【解决方案3】:

    什么是 C++17 中的std::vector 推导指南?

    Class template argument deduction 指定:“为了实例化类模板,必须知道每个模板参数,但不必指定每个模板参数。”

    这是针对std:vector 本地化的,我的意思是std:vector 只是一个类。没什么特别的。

    这是来自参考的std::vector 推理指南:

    template< class InputIt,
              class Alloc = std::allocator<typename std::iterator_traits<InputIt>::value_type>>
    vector(InputIt, InputIt, Alloc = Alloc())
      -> vector<typename std::iterator_traits<InputIt>::value_type, Alloc>;
    

    如果您对语法不熟悉,请阅读What are template deduction guides in C++17?

    为什么以及何时需要向量推导?

    当从参数中推断类型不是基于这些参数之一的类型时,您需要指南。

    x 是vector&lt;int&gt; 还是vector&lt;vector&lt;int&gt;&gt;?

    没有!

    这是一个:

    std::vector<std::vector<int>::iterator>
    

    强制一个简单的编译错误(例如通过为x 分配一个数字)将揭示其类型):

    error: no match for 'operator=' (operand types are 'std::vector<__gnu_cxx::__normal_iterator<int*, std::vector<int> >, std::allocator<__gnu_cxx::__normal_iterator<int*, std::vector<int> > > >' and 'int')
    

    PS:

    为什么我们需要那个初始化,Alloc = Alloc() 在那个指南中?

    这是一个默认参数,它允许传入一个分配器。默认意味着您不需要两个指南。

    【讨论】:

    • 为什么我们需要那个初始化,Alloc = Alloc() 在那个指南中? Alloc 还不足以代表类型本身吗?
    • @DeanSeo 这是一个默认参数。
    • @Barry 对。但是我们从演绎指南中需要的是类型,而不是参数?编译器已经知道它的 default 类型而没有 Alloc() 似乎?
    • @Dean ... 因为它是默认参数。本指南还处理传入分配器的情况。默认意味着您不需要两个指南。
    • 我同意@Barry,更新了答案(尽管我不确定是否应该这样做)。感谢您的支持!
    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 2015-12-15
    • 1970-01-01
    • 1970-01-01
    • 2018-02-11
    • 2018-07-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多