Подтвердить что ты не робот

Почему 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,

Почему это происходит?

4b9b3361

Ответ 1

Ответ подытожен в одном комментарии:

decltype дает int(&)[2], тогда как plain auto принудительно преобразует указатель (те же правила, что и вывод аргумента шаблона). Просто используйте auto&. - Xeo


@Xeo comment-answer в основном говорит, что, поскольку auto включает те же правила, что и вывод аргумента типа шаблона, 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 (whoo!). Теперь выходим из шаблона аргумента типа. В этой ситуации он управляет тем, что data является ссылкой, T должен быть выведен с исходным типом аргумента, который равен int[2][2]. Затем он применяет любую квалификацию к типу параметра к параметру, а с data квалифицированным типом является const T&, применяются квалификаторы const и &, поэтому data type 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);

LIVE CODE

Однако , если 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*! Зачем? Потому что все blabbery-blablablah выше, и некоторые магии шаблонов.

Итак, почему ошибка с int*? Поскольку std::begin ожидает тип массива, а не его меньший затухающий указатель. Таким образом, auto j = std::begin(tmp) приведет к ошибке, если tmp int*.

Как решить (также tl; dr)?

  • Хранить как есть. Используйте decltype.

  • Угадайте, что. Сделайте свою переменную auto ed ссылкой!

    auto& tmp = *i;
    

    LIVE CODE

    или

    const auto& tmp = *i;
    

    если вы не намерены изменять содержимое tmp. (Величие Джона Перди)


Мораль истории: отличный комментарий спасает человека тысячу слов.


UPDATE: добавил const к типам, указанным decltype(i) и decltype(*i), поскольку std::begin(data) вернет указатель const из-за data, также являющегося const (исправить с помощью litb, спасибо)