为什么decltype在这里工作但不是自动?

时间:2014-02-17 08:24:23

标签: c++ c++11

我的代码如下:

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,

为什么会这样?

1 个答案:

答案 0 :(得分:15)

总结在一条评论中的答案:

  

decltype产生int(&)[2],而普通auto强制指针转换(与模板参数推导相同的规则)。只需使用auto&即可。 - Xeo


@ Xeo的评论 - 答案基本上说因为auto涉及与template argument type deduction相同的规则,auto从源的数组类型中推导出指针(int*)类型(i,特别是int(&)[2])。

您的代码中有一些很棒的东西:它实际上演示了当参数是引用时模板类型推导的行为以及引用如何影响推断类型的方式。

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 s(whoo!)的两个数组的数组。现在来模板参数类型推导。在这种情况下,它规定当data引用时,T应该使用参数的原始类型推导出来,int[2][2]。然后,它将参数类型的任何限定条件应用于参数,并且data的限定类型为const T&const和{{1}我会应用限定符,因此&的类型为data

const int (&) [2][2]

LIVE CODE

但是,如果 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); 本来是非参考,模板参数类型扣除规则如果参数的类型是数组类型(例如{ {1}}),the array type shall "decay" to its corresponding pointer type,从而data成为int[2][2](如果参数为int[2][2],则为int(*)[2]}(fix courtesy of @Xeo)。< / p>


大!我刚刚解释了完全没有导致错误的部分。 (我刚刚解释了很多模板魔法)......

......没关系。现在到了错误。但在我们离开之前,请记住这一点:

const

现在,您的代码:

const

战斗前的一些先决条件:

  • 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; } (请参阅std::begin),这是指向decltype(data) == const int (&) [2][2]的指针。

现在当你decltype(i) == const int (*) [2]时,int[2]会返回decltype(*i) tmp = *i;,这是对decltype(*i)的引用(请记住取消引用一词)。因此,它也是const int(&)[2]的类型。 您使用int[2] 保留了原始类型。

然而,当你这样做时

tmp

猜猜decltype(*i)是什么:auto tmp = *i; !为什么?因为所有的blabbery-blablablah上面都有一些模板魔法。

那么,decltype(tmp)的错误原因是什么?因为 int*需要数组类型,而不是其较小的衰减指针。因此,当int*std::begin时,auto j = std::begin(tmp)会导致错误。

如何解决(也 tl; dr )?

  • 保持原样。使用tmp

  • 猜猜是什么。将您的int* ed变量作为参考!

    decltype

    LIVE CODE

    auto

    如果您不打算修改auto& tmp = *i; 的内容。 (Greatness by Jon Purdy


故事的道德:一个伟大的评论为一个人节省了千言万语。


更新:const auto& tmp = *i; 添加到tmpconst提供的类型中,因为decltype(i)会返回decltype(*i)指针由于std::begin(data)也是constfix by litb,谢谢)