【问题标题】:C++ Primer (5th ed.) : Is "16.3 Overloading and Templates" wrong in all its "more specialized" examples?C++ Primer(第 5 版):“16.3 重载和模板”在其所有“更专业”的示例中是否错误?
【发布时间】:2015-06-16 11:24:59
【问题描述】:

C++ Primer(第 5 版) - 重载和模板 - 第 16.3 节,教授在存在候选函数模板实例化的情况下的函数匹配过程。

以下是本节中使用的函数模板的声明:

using std::string;
template <class T> string debug_rep(const T &); /* 1 */
template <class T> string debug_rep(T *);       /* 2 */
// definitions not relevant for the questions

 第一个例子

string s("SO");
debug_rep(&s);

然后说生成的实例化将是:

  1. debug_rep(const string *&amp;)T 绑定到 string *
  2. debug_rep(string *)

Q1 #1 是否正确?它不应该实例化debug_rep(string* const &amp;) 吗?

 第二个例子

const string *sp = &s;
debug_rep(sp); //string literal type is const char[10]

然后说生成的实例化将是:

  1. debug_rep(const string *&amp;)T 绑定到 const string *
  2. debug_rep(const string *)

因此,两个实例化的候选者都将提供完全匹配,在更专业的模板上进行选择 (-> #2)

Q2.1 #1 是否正确?它不应该实例化debug_rep(const string* const &amp;)吗?

Q2.2 假设实例化的函数就是上面那个,我们可以确定它不再是完全匹配了吗?

 第三个例子

debug_rep("SO world!"); //string literal type is const char[10]

然后说生成的实例化将是:

  1. debug_rep(const T &amp;)T 绑定到 char[10]
  2. debug_rep(const string *)

因此,两个实例化的候选者都将提供完全匹配,在更专业的模板上进行选择 (-> #2)

Q3.1 #1 中为T 推断的类型是否正确?不应该是const char[10]吗?

Q3.2 假设T 的推导类型实际上就是上面那个,我们可以确定它不再是完全匹配了吗?

【问题讨论】:

  • 能否请对问题投反对票的用户详细说明其原因,以便我可以尝试将我的问题提高到标准?
  • 是的,我认为作者在这一章上有点落伍了。我在 16.2.5 结束时发现了另一个错误 - 与非模板函数一样,第一个版本将绑定到可修改的右值,第二个版本将绑定到左值或 const 右值。 不正确,请尝试传递f 的非 const 左值表达式,第一个被调用,而不是第二个(由于引用折叠)。

标签: c++ templates type-deduction


【解决方案1】:

你得到了这些声明:

using std::string;
template <class T> string debug_rep(const T &); /* 1 */
template <class T> string debug_rep(T *);       /* 2 */

在调用中

string s("SO");
debug_rep(&s);

&amp;s 产生一个string*,当Tstring* 时,它只能匹配(1) 的T const&amp;。对于 (2) 中的 T*,有一个匹配 T 绑定到 string。所以,如果你的引用是正确的,这本书是错误的

debug_rep(const string *&)

作为一个可能的实例化:没有这样的。

T = string* 产生的实例化将改为

debug_rep( string* const& )

但是会调用哪个实例化呢?

作为一般规则,最简单的匹配更好,但我从来没有记住确切的规则,所以,我问 Visual C++(因为它的typeid(T).name() 默认产生可读的类型名称):

#include <iostream>
#include <string>
#include <typeinfo>
using namespace std;

template< class T >
struct Type {};

template <class T> auto debug_rep( T const& )   // 1
    -> string
{ return string() + "1 --> T = " + typeid(Type<T>).name(); }

template <class T> auto debug_rep( T* )         // 2
    -> string
{ return string() + "2 --> T = " + typeid(Type<T>).name(); }

auto main() -> int
{
    string s( "SO" );
    cout << debug_rep( &s ) << endl;
    cout << "The type 'int const' is shown as " << typeid(Type<int const>).name() << endl;
}

它说:

2 --> T = struct Type,class std::allocator > 类型“int const”显示为 struct Type

对于你的第二个和第三个例子等等:显然作者对const有一些混淆。

【讨论】:

  • typeid 和模板不能很好地相处,因为 typeid 丢弃了 T 可能的 cv 限定符。
  • @black:对。在上面的代码中并不重要,它只是显示选择了哪个模板。但是要在您想要的位置获取外部const 信息,您可以定义template&lt;class T&gt; struct Type{};,然后编写例如typeid&lt;Type&lt;T&gt;&gt;.name().
  • @black:想一想,也许最好在这里也这样做。正在更新答案……谢谢。
  • 避免使用 typeid 丢弃顶级 const 限定的简单技巧!你不会碰巧有一个在给定引用类型时不会丢弃引用的人吗? ; )
  • @AdN 有一个模板函数,它接受一个 T&& 并尝试实例化那个未定义的模板类。
【解决方案2】:

编辑:感谢 Alf 的回答,以及在使用 typeid 时保存有关类型的完整信息的优雅技巧,我能够编写一个程序来解决我的大部分问题(从std::string 更改为int 以提高输出可读性。)
完整代码可编辑运行rextester online IDE

让我们定义一些类和方法:

template <class> class Type{}; // Allowing to get full type information with typeid
 
template <class T> std::string typeStr()
{ return typeid(Type<T>).name(); }

template <class T> void debug_rep(const T &a) /* 1 */
{
    std::cout << "[1] T type is: "    << typeStr<T>()
              << "\t| arg type is: " << typeStr<decltype(a)>() << std::endl;
}

template <class T> void force_1(const T &a)   /* 1 */
{
    std::cout << "[forced 1] T type is: "    << typeStr<T>()
              << "\t| arg type is: " << typeStr<decltype(a)>() << std::endl;
}

template <class T> void debug_rep(T *a)       /* 2 */
{
    std::cout << "[2] T type is: "    << typeStr<T>()
              << "\t| arg type is: " << typeStr<decltype(a)>() << std::endl;
}

示例 1

跑步:

std::cout << "---First example---" << std::endl;
int i = 41;
debug_rep(&i);
force_1(&i);

显示:

---First example---
[2] T type is: class Type<int>  | arg type is: class Type<int *>
[forced 1] T type is: class Type<int *> | arg type is: class Type<int * const &>

Q1:可以说,当我们调用force_1时,实例化了#1对应的模板,参数类型为int * const &amp;,所以书不正确,实例化的候选# 1 会是

  1. debug_rep(int* const &amp;)

示例 2

跑步:

std::cout << "---Second example---" << std::endl;
const int *ip = &i;
debug_rep(ip);
force_1(ip);

显示:

---Second example---
[2] T type is: class Type<int const >   | arg type is: class Type<int const *>
[forced 1] T type is: class Type<int const *>   | arg type is: class Type<int const * const &>

Q2.1:调用force_1,我们注意到参数类型将是int const * const &amp;,所以这本书在参考文献中缺少 const 限定。实例化的候选者实际上是:

  1. debug_rep(const int * const &amp;)

Q2.2 第二个候选者是debug_rep(const int *),它与ip(指向常量整数的指针)完全匹配。要检查第一个候选人的排名是否较低,让我们编写:

void debug_rep_plain_b(const int * const &)   /* 1 */
{ std::cout << "[plain 1]" << std::endl;}

void debug_rep_plain_b(const int *)           /* 2 */
{ std::cout << "[plain 2]" << std::endl; }

如果我们尝试编译:

debug_rep_plain_b(ip)

歧义调用存在编译错误:所以 Q2.2 的答案是否定的,仍然是完全匹配!对于模板化版本,编译器实际上使用了关于最专业模板的规则来解决歧义。
即使推导的候选项有错误,本书是正确的,因为该示例使用最专业的模板说明了重载解决方案。

示例 3

跑步:

std::cout << "---Third example---" << std::endl;
const int ia[3] = {1, 2, 3};
debug_rep(ia);
force_1(ia);

显示:

---Third example---
[2] T type is: class Type<int const >   | arg type is: class Type<int const *>
[forced 1] T type is: class Type<int const [3]> | arg type is: class Type<int const (&)[3]>

Q3.1 CL 推导出T 的类型是const 整数数组,所以会误读。

BUT 结果与 GCC 或 Clang 不一致,会输出:

---Third example---
[2] T type is:4TypeIKiE | arg type is: 4TypeIPKiE
[forced 1] T type is:4TypeIA3_iE    | arg type is: 4TypeIRA3_KiE

有趣的部分是:
[强制1] T型为:4TypeIA3_iE
这意味着他们将 T 推断为 3 个 -const 整数的数组(因为 _i,而不是 _Ki),这与本书一致。

这个问题我得再开一个问题,我看不懂GCC和Clang操作的类型推导...

【讨论】:

  • webcompiler.cloudapp.net 的 Visual C++ 19 在线编译器在第三个示例中打印 [forced 1] T type is: class Type&lt;int [3]&gt;。可能是早期版本中的错误。 -- 如果我们不理会“原始答案”,那你为什么不直接删除它呢?
  • 感谢您的评论!有趣的是,最新版本的 CL 与 GCC 和 Clang 一致:正如您所指出的,这种行为可能是预期的。我只需要了解原因(我觉得推断出const int 的数组更直观)。我不确定完全删除答案是否是一种良好的礼仪,但由于它会引入噪音并且没有人评论其内容,所以你是对的,我会删除它。
  • “我只需要了解原因” 一个有趣的问题。事实证明,这取决于缺陷报告CWG 1059CWG 1610。基本上,元素类型的 const 限定是(也是)数组类型的一部分。我会试着找出为什么它会被这样对待。另见CWG 112
  • @dyp 感谢您的指点。在我看来,这本身就是一个问题,所以我将其作为一个新问题提出:stackoverflow.com/q/30893036/1027706
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-05
  • 2022-01-25
  • 2012-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多