【问题标题】:effects of a `const` as a template argument to a move constructor`const` 作为移动构造函数的模板参数的效果
【发布时间】:2019-06-29 00:35:41
【问题描述】:

考虑以下情况,在模板参数中添加const 有什么效果?

案例 1:void func() 通过复制获取 T 类型的参数

#include <iostream>
#include <boost/type_index.hpp>

template<typename T>
void func(T val_)
{
    using boost::typeindex::type_id_with_cvr;
    std::cout<< "type of `val_` is `" << type_id_with_cvr< decltype(val_) >().pretty_name() << "`" <<std::endl;
}

int main()
{
    int *f = new int(2);  

    func<int>(2);                // type of `val_` is `int` - OK
    func<int*>( f );             // type of `val_` is `int*` - OK
    func<const int*>( f );       // type of `val_` is `int const*` - OK
    func<const int* const>( f ); // type of `val_` is `int const* const` - OK
}

第一个案例没有问题...很清楚发生了什么。

案例2:void func() 通过引用获取T 类型的参数

#include <iostream>
#include <boost/type_index.hpp>

template<typename T>
void func(T &val_)
{
    using boost::typeindex::type_id_with_cvr;
    std::cout<< "type of `val_` is `" << type_id_with_cvr< decltype(val_) >().pretty_name() << "`" <<std::endl;
}

int main()
{
    int *f = new int(2);  

    func<int>(2);                // error - OK
    func<int*>( f );             // type of `val_` is `int*&` - OK
    func<const int*>( f );       // error: no known conversion from 'int *' to 'const int *&' - OK
    func<const int* const>( f ); // type of `val_` is `int const* const&` - ???
}

案例 3:void func() 通过转发/通用引用获取 T 类型的参数

#include <iostream>
#include <boost/type_index.hpp>

template<typename T>
void func(T &&val_)
{
    using boost::typeindex::type_id_with_cvr;
    std::cout<< "type of `val_` is `" << type_id_with_cvr< decltype(val_) >().pretty_name() << "`" <<std::endl;
}

int main()
{
    int *f = new int(2);  

    func<int>(2);                // type of `val_` is `int&&` - OK
    func<int*>( f );             // error: no known conversion from 'int *' to 'int *&&' - OK
    func<const int*>( f );       // type of `val_` is `int const*&&` - ???
    func<const int* const>( f ); // type of `val_` is `int const* const&&` - ???
}

问题 #1: 在案例 #2 中,为什么 func&lt;const int* const&gt;( f ); 仅通过添加 rhs const 就被接受了?

问题 #2: 在案例 #3 中,为什么仅通过添加 const 就可以接受对 func 的最后两次调用?

问题 #3: 在案例 #3 中,使用转发引用的好处是,如果函数参数是左值,那么 val_ 的类型将是左值引用。如果参数是一个右值,那么val_ 的类型将是一个右值。因此,传递左值f 应该使T 成为int*&amp;。当您没有在函数调用时指定模板参数int* 时,情况确实如此。但是,当您指定模板参数int* 时,结果是int*&amp;&amp;。为什么?

【问题讨论】:

  • 我们假设推断转发/通用引用的参数。
  • 对于案例 1,您可以使用 func&lt;int*&amp;&gt;/func&lt;int*&amp;&amp;&gt; 处理 case2/case3。

标签: c++ templates constants


【解决方案1】:

f 的类型是int*。只有int*&amp; 或int* const&amp; 可以直接绑定到它。

对于案例#2,int* 和int const* 是不同的类型,因此对int cont* 的引用不能直接绑定到f。存在从 int* 到 int const* 的转换,但是左值引用到非 const 不能绑定到结果对象,因为它是右值。一个 lvalue-reference-to-const 可以 绑定到一个右值,因此,由于存在从 int* 到 int const* const 的隐式转换,当您将 const 添加到临时 @ 类型时可以创建987654334@,并且可以绑定引用。

这与以下工作的原因相同:

void foo(const std::string& str) {}
int main() { foo("hello"); }

虽然以下没有:

void foo(std::string& str) {}
int main() { foo("hello"); }

对于案例 #3,int* 和 int const* 是不同的类型。由于存在从int* 到int const* 和int const* const 的隐式转换,因此会创建临时副本。这些临时副本是右值,因此右值引用可以毫无问题地绑定到它们。当您使用int* 作为模板参数时,类型已经匹配,因此不会生成临时副本。由于f 是一个左值,右值引用不能绑定到它,你会得到一个错误。


回答您编辑的最后一个问题:仅当您推断模板参数时才会出现转发引用。使它们起作用的是模板参数推导和引用折叠规则,因此如果您指定显式模板参数,您将永远不会获得转发引用。

例如,给定以下函数模板:

template <typename T>
void foo(T&& obj) {}

如果传递int 类型的左值(即foo(some_int_var)),T 将被推导出为int&amp;,因此obj 的类型为int&amp; &amp;&amp;。应用引用折叠规则后,将折叠为int&amp;:一个 lvalue-reference-to-int。另一方面,如果传递int 类型的右值(即foo(42)),则T 将被推导出为int,因此obj 的类型将为int&amp;&amp;:rvalue-reference -to-int。

【讨论】:

  • 我花了一些时间才理解案例 #2 的第一部分,但我对隐式转换的东西仍然有点模糊。在答案的第一部分中,您说 “无法创建临时的 const int *,因为左值引用到非 const 无法绑定到右值”,但在您回答的第二部分,您说“存在从int* 到const int * 和const int * const 的隐式转换,创建了临时副本。”。有什么不同?能否请您重新措辞那部分?
  • @ConstantinosGlynos 改写了那部分。转换会产生一个匿名临时对象,也就是一个右值。存在转换,但在情况 #2 中这还不够,因为 lvalue-reference-to-non-const 无法绑定到结果对象。
  • 谢谢!我不知道转发引用只出现在类型推导中,第二部分现在更有意义。
【解决方案2】:

在情况#2 中,第一个问题应该是:为什么func&lt;const int *&gt;(f) 不好?毕竟,通常您可以在需要const int * 的地方使用int *。它失败的原因是即使int * 是-a const int *(即,您可以取消引用以获取int 的事物是您可以取消引用以获取const int),reference 对 int * 的引用不是对 const int * 的引用,因为您可以“通过”它进行分配。如果您被允许致电func&lt;const int *&gt;(f),那么func 的某些不同版本可以例如将真正的东西分配给const int * 到f,之后您就可以修改它所指向的内容,这将是错误的。

现在应该很明显为什么第二个const 修复了它:你不能通过const 引用来更改f 的值。

案例#3 有点类似。同样,要问的第一个问题不是为什么您的第三个和第四个案例有效,而是您的第二个案例为什么失败。它失败了,因为您不允许将右值引用 (&amp;&amp;) 带到左值(有名称的事物,例如 f)。为什么不?因为这些事情的原因之一是能够获得不同的行为,具体取决于传递的东西是否是临时的(因此可以安全地破坏),而这样做的方式是禁止将T 类型的左值转换为T &amp;&amp; 类型的右值引用。 (因此,您可以编写一个采用T &amp; 的函数和另一个采用T &amp;&amp; 的函数,并让后者执行更有效的破坏操作。)

但是你对该左值所做的任何事情都会阻止它成为左值并使func 的调用再次合法。例如,您可以向其添加 0。或者您可以将其转换为不同的类型。而且,啊哈!这就是案例#3 中第三个和第四个示例中发生的情况:传递给func 的内容实际上不是f 本身,而是const_cast&lt;const int *&gt;(f) 之类的东西,这不再是左值和一切都很好。

【讨论】:

  • 我喜欢你在答案第一部分的解释,这有助于理解案例 #2 的结果。但是你对第三种情况的解释不是很清楚。使用转发引用的好处是,如果您将左值作为参数传递,则类型将是左值引用。因此,传递f,它是一个左值,应该使T 成为int*&amp;。当您没有在函数调用时指定模板参数int* 时,情况确实如此。但是,当您指定模板参数int* 时,结果是int*&amp;&amp;。为什么? example
  • @ConstantinosGlynos:正如您所指出的,当您允许推断T 时,它变为int*&amp;。这与T&amp;&amp; 参数变为int*&amp; 不同(尽管它确实如此)。当您明确提供int* 时,在下一种情况下T 是int*&amp; 会很奇怪。我想你不会对此感到困惑。然后当T 为int* 时,T&amp;&amp; 参数变为int*&amp;&amp;,这直接遵循引用折叠规则。当您指定int* 时,左值引用无处出现——T 不是一个,参数类型T&amp;&amp; 也不是。
猜你喜欢
  • 2016-09-02
  • 2017-05-10
  • 1970-01-01
  • 2017-05-23
  • 2013-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多