Ответ 1
Ответ подытожен в одном комментарии:
decltype
даетint(&)[2]
, тогда как plainauto
принудительно преобразует указатель (те же правила, что и вывод аргумента шаблона). Просто используйте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);
Однако , если 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;
или
const auto& tmp = *i;
если вы не намерены изменять содержимое
tmp
. (Величие Джона Перди)
Мораль истории: отличный комментарий спасает человека тысячу слов.
UPDATE: добавил const
к типам, указанным decltype(i)
и decltype(*i)
, поскольку std::begin(data)
вернет указатель const
из-за data
, также являющегося const
(исправить с помощью litb, спасибо)