is_const在引用中的作用与预期不符。

16

我写了一个测试程序:

#include <iostream> 
#include <type_traits> 
using namespace std; 
template<class T> 
void f(T&& t) 
{ 
     cout<<is_const<T>()<<endl; 
     //++t; 
} 
int main() { 
     const int i=0; 
     f(i); 
     return 0; 
} 

它输出了“0”,显示T不是常量!这很奇怪。然后我修改了f:
template<class T> 
void f(T&& t) 
{ 
     cout<<is_const<T>()<<endl; 
     ++t; 
} 

然后出现了编译错误,表示我们正在修改只读的t。 那么,t是否可以被修改呢?在我的程序中有什么误解吗?


@101010 用法正确。is_const具有适当的转换运算符。 - juanchopanza
@juanchopanza 谢谢你提供的信息,我之前不知道。 - 101010
@juanchopanza 在 cout<<is_const<T>()<<endl; 语句中,什么会将其强制转换为布尔类型? - xaxxon
7
try is_const<std::remove_reference<T>>::value - M.M
@xaxxon 嗯,没有 ostream& operator<<(ostream&, is_const<T>),所以选择的余地不大。 - juanchopanza
如果你有兴趣学习关于类型推断的所有知识 - 包括为什么T不是引用类型 - 那么可以阅读Scott Meyers所著的《Effective Modern C++》,这本书完整而易懂地讲解了类型推断以及更多相关话题。 - davidbak
5个回答

15

请参阅std::is_const:

如果T是一个const限定类型(即const或const volatile),则提供成员常量值等于true。对于任何其他类型,值为false。

t被声明为转发引用。因此对于您的代码,T将被推断为const int&,它是一个引用。引用不能被const限定,它本身不会是const。确切地说,不存在const引用(即int& const),因为引用无法再次重新绑定。 const int&是一个引用const int;请注意,t因此无法修改。

来自标准,$8.3.2/1 References [dcl.ref]

除非cv限定符是通过typedef名称([dcl.typedef],[temp.param])或decltype-specifier([dcl.type.simple])引入的,否则cv限定符的引用是不合法的,在这种情况下,cv限定符将被忽略。

更多示例请见cppreference

std::cout << std::is_const<int>::value << '\n'; // false
std::cout << std::is_const<const int>::value  << '\n'; // true
std::cout << std::is_const<const int*>::value  << '\n'; // false
std::cout << std::is_const<int* const>::value  << '\n'; // true
std::cout << std::is_const<const int&>::value  << '\n'; // false

4
无论是否可修改取决于推断出的类型的类型,这取决于传递的变量的类型。在此情况下,您传递了一个常量整数,因此t的类型为const int&,因为您将其作为转发引用接受。
至于为什么is_const返回false,那是因为T是引用类型,而引用永远不会是const。

3

你的模板函数(即f)以转发引用(也称为通用引用)作为参数。决定T推导的规则被称为引用折叠规则。这些规则总结如下:

  1. T& & 变成 T&
  2. T& && 变成 T&
  3. T&& & 变成 T&
  4. T&& && 变成 T&&

现在,根据引用折叠规则,当您将 int const i 作为参数传递给 f 时,T 将被推导为 int const&

根据 C++ 标准的表格 52,如果 Tconst 限定的,则 is_const 将求值为 true

enter image description here

此外,在C++标准的§20.13.4.3/p5类型属性[meta.unary.prop]中,有以下关于is_const类型特征如何工作的示例:

[Example:

is_const<const volatile int>::value // true
is_const<const int*>::value // false
is_const<const int&>::value // false
is_const<int[3]>::value // false
is_const<const int[3]>::value // true

— end example ]

正如您在第三行所看到的,我们的情况 is_const 评估为 false 。为什么?因为传递给 is_const 作为模板参数的类型是引用类型。现在,引用本质上是 const 的,因为您无法更改它们所引用的内容,但它们没有 const 限定符。因此,具有引用类型的 is_const 将评估为 false

0

最高级别的答案是,引用没有 cv 限定。只有它们所指向的类型可以被 cv 限定。

但这是一个单词 const 放置位置很重要的时候。当你调用:

template<class T> 
void f(T&& t) 

对于类型为const int的左值,T被推导为什么?你可能会说const int&(这是正确的),但它看起来像是const (int&)类型。因此可能会混淆Tconst的原因。

但如果你说它被推导为int const&(与之前相同的类型),这里就没有可能的混淆了,也许更不足为奇的是std::is_const<int const&>std::false_type


0
可能的解决方案是在重载中拦截const类型:
template<class T> 
void f(T&& t) 
{ 
     ++t; 
} 

template<class T> 
void f(const T& t) 
{ 
     std::cout << "const here" << std::endl;
} 

那么对常量对象的引用将由第二个函数处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接