auto std::initializer_list поведение при присваивании.
От: SomeOne_TT  
Дата: 22.07.18 17:05
Оценка:
#include "stdafx.h"
#include <initializer_list>
#include <string>
#include <iostream>
void test_func()
{
    auto strides = { 1, 1, 1, 1 };
    std::cout << *strides.begin() << " " << *(strides.begin() + 1) << " "
        << *(strides.begin() + 2) << " "
        << *(strides.begin() + 3) << " "
        <<  std::endl;

    
    strides = { 1, 2, 2, 1 };
    std::cout << *strides.begin() << " " << *(strides.begin() + 1) << " "
        << *(strides.begin() + 2) << " "
        << *(strides.begin() + 3) << " "
        << std::endl;
}
int main()
{
    test_func();
    return 0;
}



MSVC 2015 debug x86/x64
1 1 1
1
1 2 2
1

MSVC 2015 release x86/x64
1 1 1
1
1 1 1
1

MSVC 2017 debug x86/x64
1 1 1
1
1 2 2
1

MSVC 2017 release x86 присваивание полностью сломано и выдает значения из глубин океана
1 1 1 1
1948491747 2715648 13964870 1948491771

ЧЯДНТ?
Re: auto std::initializer_list поведение при присваивании.
От: watchmaker  
Дата: 22.07.18 20:31
Оценка:
Здравствуйте, SomeOne_TT

std::initialized_list — это такая ссылка на временный массив. Плюс особое правило про продление времени жизни этого массива. Но вторая половина твоей странноватой функции, конечно же, не попадает под его действие (а вот первая строка функции — попадает).

SO_>    strides = { 1, 2, 2, 1 };

Тут массив 1221 перестаёт существовать с окончанием выражения. А ссылка/указатель на него у тебя остаётся. И потом ты что-то через него делаешь. И то, что многие компиляторы создают эти массивы в compile-time (со временем жизни равным времени жизни всей программы) не играет особой роли.

SO_>ЧЯДНТ?

Используешь std::initialized_list для чего-то странного.
Re[2]: auto std::initializer_list поведение при присваивании.
От: SomeOne_TT  
Дата: 23.07.18 09:00
Оценка:
W>std::initialized_list — это такая ссылка на временный массив. Плюс особое правило про продление времени жизни этого массива. Но вторая половина твоей странноватой функции, конечно же, не попадает под его действие (а вот первая строка функции — попадает).

W>
SO_>    strides = { 1, 2, 2, 1 };

W>Тут массив 1221 перестаёт существовать с окончанием выражения. А ссылка/указатель на него у тебя остаётся. И потом ты что-то через него делаешь. И то, что многие компиляторы создают эти массивы в compile-time (со временем жизни равным времени жизни всей программы) не играет особой роли.

Я благодарен за ответ и понимаю, что просьба потыкать в пункты стандарта была бы хамством, но все же не вижу отличия первой части функции от второй.
Роль в присваивании?


W>Используешь std::initialized_list для чего-то странного.


Ловил хитрый баг, который свелся к данному коду.
Re: auto std::initializer_list поведение при присваивании.
От: reversecode google
Дата: 23.07.18 09:27
Оценка:
http://coliru.stacked-crooked.com
g++ -std=c++17 -O3 -Wall -pedantic main.cpp && ./a.out

main.cpp: In function 'void test_func()':
main.cpp:387:38: warning: '<anonymous>' may be used uninitialized in this function [-Wmaybe-uninitialized]
     std::cout << *strides.begin() << " " << *(strides.begin() + 1) << " "
                                      ^~~
main.cpp:387:71: warning: '*((void*)&<anonymous> +4)' may be used uninitialized in this function [-Wmaybe-uninitialized]
     std::cout << *strides.begin() << " " << *(strides.begin() + 1) << " "
                                                                       ^~~
main.cpp:388:38: warning: '*((void*)&<anonymous> +8)' may be used uninitialized in this function [-Wmaybe-uninitialized]
         << *(strides.begin() + 2) << " "
                                      ^~~
main.cpp:389:38: warning: '*((void*)&<anonymous> +12)' may be used uninitialized in this function [-Wmaybe-uninitialized]
         << *(strides.begin() + 3) << " "
                                      ^~~
1 1 1 1 
0 0 0 0

msvc не дает никаких варнингов ? странный компиль
Re[3]: auto std::initializer_list поведение при присваивании.
От: Croessmah  
Дата: 23.07.18 09:44
Оценка: +1
Здравствуйте, SomeOne_TT, Вы писали:

SO_>Я благодарен за ответ и понимаю, что просьба потыкать в пункты стандарта была бы хамством, но все же не вижу отличия первой части функции от второй.

SO_>Роль в присваивании?


При копировании std::initializer_list копирования элементов не происходит. Копируются указатели (или еще чего, устройство initialize_list зависит от реализации). Сам внутренний массив также является временным и "привязывается" к инициализируемому списку.

11.6.4 List-initialization
6. The array has the same lifetime as any other temporary object (15.2), except that initializing an initializer-list object from the array extends the lifetime of the array exactly like binding a reference to a temporary

strides = { 1, 2, 2, 1 }//Здесь создается временный initializer_list;

Для наглядности, переписать это присваивание можно так:
{
   std::initializer_list tmp_list = { 1, 2, 2, 1 };//Представляет временный объект из присваивания. К нему привязан временный массив.
   strides = tmp_list;//Скопировались "указатели"
}//объект tmp_list уничтожился, а вместе с ним и временный массив. "Указатели" в strides более не действительны


Вот еще пример подобной ошибки: http://rextester.com/WMMU56060
Re[4]: auto std::initializer_list поведение при присваивании.
От: SomeOne_TT  
Дата: 23.07.18 11:23
Оценка:
Здравствуйте, Croessmah, Вы писали:

C>Здравствуйте, SomeOne_TT, Вы писали:


SO_>>Я благодарен за ответ и понимаю, что просьба потыкать в пункты стандарта была бы хамством, но все же не вижу отличия первой части функции от второй.

SO_>>Роль в присваивании?


C>При копировании std::initializer_list копирования элементов не происходит. Копируются указатели (или еще чего, устройство initialize_list зависит от реализации). Сам внутренний массив также является временным и "привязывается" к инициализируемому списку.

C>

C>11.6.4 List-initialization
C>6. The array has the same lifetime as any other temporary object (15.2), except that initializing an initializer-list object from the array extends the lifetime of the array exactly like binding a reference to a temporary

C>[ccode]

Вот этого понять не могу. "Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference" . Почему в одном случае это время до последующего присваивания, а в другом — до конца жизни expression?
Re[5]: auto std::initializer_list поведение при присваивании
От: SomeOne_TT  
Дата: 07.08.18 15:44
Оценка: 1 (1)
Здравствуйте, SomeOne_TT, Вы писали:


SO_>Вот этого понять не могу. "Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference" . Почему в одном случае это время до последующего присваивания, а в другом — до конца жизни expression?


Все верно написали постом выше.
Немного подробностей для тех, кто также туговат, как и я.

std::initializer_list — это весьма простой класс (опущу методы для простоты)

gcc:
  template<class _E>
  class initializer_list {
    typedef size_t size_type;
    typedef const _E* iterator;
    iterator _M_array;
    size_type _M_len;
  };


MSVC:


template<class _Elem>
    class initializer_list
    {    // list of pointers to elements
public:
    typedef _Elem value_type;
    typedef const _Elem& reference;
    typedef const _Elem& const_reference;
    typedef size_t size_type;

    typedef const _Elem* iterator;
    typedef const _Elem* const_iterator;


private:
    const _Elem *_First;
    const _Elem *_Last;
    };


Как видно, это либо указатель на начало массива + размер, либо пара указателей на начало и конец массива.

Когда создается auto my_list = {1,2,3,4};
происходит вызов конструктора my_list, компилятор создает temporary array и передает его параметры в конструктор. Время жизни
этого массива определяется правилами стандарта

Так как initializer_list — это POD тривиальный простой тип, который допускает создание тривиального оператора присваивания и в нем он явно не запрещен, компилятор генерирует неявный оператор, который
при исполнении просто копирует все поля класса.

В то же время, когда происходит присваивание уже созданному объекту нового braced-list, происходит следующее:
"When you assign to an object, the copy assignment operator, or in C++11 move assignment operator is called. If a temporary object is created on the right-hand side of the expression, the standard constructor for that object is called to initialize the temporary object, then the copy assignment operator (or move assignment operator) is called on the object being assigned to, with the temporary object as the argument"


Следовательно, для присваивания уже созданному объекту my_list компилятор
а) создает временный массив и инициализирует его вторым braced-list
б) создает новый объект класса initializer_list, время жизни которого не будет превышать времени жизни временного массива (компилятор постарается)
в) для объекта my_list вызывает неявный оператор присваивания, который просто скопирует содержимое указателей нового объекта
г) после чего тут же временный объект, который был создан только для присваивания, будет уничтожен, а вместе с ним и временный массив
д) my_list теперь обладает указателем/указателями на область памяти, которая уже деаллоцированна.

P.S.
Подобное поведение(незапрещенный оператор присваивания) встречало критику как на этапе proposal'а этого типа, так и
позже, например
Отредактировано 07.08.2018 16:01 SomeOne_TT . Предыдущая версия . Еще …
Отредактировано 07.08.2018 15:53 SomeOne_TT . Предыдущая версия .
Отредактировано 07.08.2018 15:51 SomeOne_TT . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.