为什么 decltype 在这里有效但不能自动?



我有如下代码:

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)会在tmpint*时导致错误。

如何解决(也TL;博士)?

  • 保持原样。使用decltype.

  • 你猜怎么着。让您的auto变量成为参考!

    auto& tmp = *i;
    

    实时代码

    const auto& tmp = *i;
    

    如果您不打算修改tmp的内容。(乔恩·珀迪的伟大)


故事的寓意:一个伟大的评论可以节省一个人千言万语。


更新:decltype(i)decltype(*i)给出的类型添加了const,因为std::begin(data)会返回一个const指针,因为data也被const(由 litb 修复,谢谢)

相关内容

最新更新