我有如下代码:
template <typename T, typename sepT = char>
void print2d(const T &data, sepT sep = ',') {
for(auto i = std::begin(data); i < std::end(data); ++i) {
decltype(*i) tmp = *i;
for(auto j = std::begin(tmp); j < std::end(tmp); ++j) {
std::cout << *j << sep;
}
std::cout << std::endl;
}
}
int main(){
std::vector<std::vector<int> > v = {{11}, {2,3}, {33,44,55}};
print2d(v);
int arr[2][2] = {{1,2},{3,4}};
print2d(arr);
return 0;
}
如果我将decltype
更改为auto
,它不会编译和抱怨(部分错误):
2d_iterator.cpp: In instantiation of ‘void print2d(const T&, sepT) [with T = int [2][2]; sepT = char]’:
2d_iterator.cpp:21:21: required from here
2d_iterator.cpp:9:36: error: no matching function for call to ‘begin(const int*&)’
2d_iterator.cpp:9:36: note: candidates are:
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/string:53:0,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/bits/locale_classes.h:42,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/bits/ios_base.h:43,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/ios:43,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/ostream:40,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/iterator:64,
为什么会这样?
答案总结在一个评论中:
decltype
产生int(&)[2]
,而纯auto
强制指针转换(与模板参数推导相同的规则)。只需使用auto&
.- 异
@Xeo的注释答案基本上是说,因为auto
涉及与模板参数类型推导相同的规则,auto
从源的数组类型(i
,特别是int(&)[2]
)中推导出一个指针(int*
)类型。
您的代码中有一些很棒的东西:它实际上演示了当参数是引用时模板类型推断的行为方式,以及引用如何影响类型的推导方式。
template <typename T, typename sepT = char>
void print2d(const T &data, sepT sep = ',') {
...
}
...
int arr[2][2] = {{1,2},{3,4}};
print2d(arr);
你可以看到data
的类型是const T&
,对const T
的引用。现在,它与arr
一起传递,其类型为int[2][2]
,这是一个由两个int
数组组成的数组(哇!现在来模板参数类型推导。在这种情况下,它规定以data
为参考,T
应用参数的原始类型(即int[2][2]
)推导。然后,它将参数类型的任何限定应用于参数,并且const T&
data
的限定类型,应用const
和&
限定符,因此data
的类型是const int (&) [2][2]
。
template <typename T, typename sepT = char>
void print2d(const T &data, sepT sep = ',') {
static_assert(std::is_same<T, int[2][2]>::value, "Fail");
static_assert(std::is_same<decltype(data), const int(&)[2][2]>::value, "Fail");
}
...
int arr[2][2] = {{1,2},{3,4}};
print2d(arr);
实时代码
但是,如果data
是非引用,则模板参数类型推导规则,如果参数的类型是数组类型(例如int[2][2]
),数组类型应"衰减"为其相应的指针类型,从而使int[2][2]
int(*)[2]
(如果参数const
则加const
)(由@Xeo提供)。
伟大!我只是解释了完全不是导致错误的部分。(我刚刚解释了大量的模板魔法)...
。没关系。现在到错误。但在我们开始之前,请记住这一点:
auto == template argument type deduction
+ std::initializer_list deduction for brace init-lists // <-- This std::initializer_list thingy is not relevant to your problem,
// and is only included to prevent any outbreak of pedantry.
现在,您的代码:
for(auto i = std::begin(data); i < std::end(data); ++i) {
decltype(*i) tmp = *i;
for(auto j = std::begin(tmp); j < std::end(tmp); ++j) {
std::cout << *j << sep;
}
std::cout << std::endl;
}
战斗前的一些先决条件:
decltype(data) == const int (&) [2][2]
decltype(i) == const int (*) [2]
(见std::begin
),它是指向int[2]
的指针。
现在当你做decltype(*i) tmp = *i;
时,decltype(*i)
会返回const int(&)[2]
,一个对int[2]
的引用(记住取消引用这个词)。因此,它也tmp
的类型。通过使用decltype(*i)
保留了原始类型。
但是,当您这样做时
auto tmp = *i;
猜猜decltype(tmp)
是什么:int*
!为什么?因为上面所有的胡说八道,还有一些模板魔法。
那么,为什么会出现错误int*
?因为std::begin
需要数组类型,而不是其较小的衰减指针。因此,auto j = std::begin(tmp)
会在tmp
int*
时导致错误。
如何解决(也TL;博士)?
保持原样。使用
decltype
.你猜怎么着。让您的
auto
变量成为参考!auto& tmp = *i;
实时代码
或
const auto& tmp = *i;
如果您不打算修改
tmp
的内容。(乔恩·珀迪的伟大)
故事的寓意:一个伟大的评论可以节省一个人千言万语。
更新:向decltype(i)
和decltype(*i)
给出的类型添加了const
,因为std::begin(data)
会返回一个const
指针,因为data
也被const
(由 litb 修复,谢谢)