cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Расслабить требования к передаче по ссылке временных объектов (как в MSVC) #367

Open apolukhin opened 3 years ago

apolukhin commented 3 years ago

Перенос предложения: голоса +0, -4 Автор идеи: Даниил Милютин

Пример.

struct ReferenceType
{
    ReferenceType(int* p, int s): ptr(p), size(s) {}
    ~ReferenceType() {}
    // contract:
    //   some non-allocating functionality
    //   data can be accessed and modified
    //     but not allocated/deallocated
    int at(int i) const { return ptr[i]; }
    int& at(int i) { return ptr[i]; }
protected:
    int* ptr = nullptr;
    int  size = 0;
};

struct ValueType: ReferenceType
{   
    // just memory management

    ValueType(int N): ReferenceType(new int[N], N) {}
    ~ValueType() { delete[] ptr; }
};

void do_smth(ReferenceType& w) { w.at(0) = 10; }
void do_smth_rvr(ReferenceType&& w) { w.at(0) = 10; }

void do_other(int& i) {}

void do_other_rvr(int&& i) { do_other(i); }

int main() {
    ValueType s(3);
    do_smth(s); // ok // converted to ReferenceType&

    int data[3];
    //do_smth(ReferenceType(+data, 3));// error // ok - only MSVC
    //do_smth(ValueType(3));     // error // ok - only MSVC

    do_other(data[0]); // ok
    //do_other(10); // error. And that's fine for me!
                     // MSVC said: 'void do_other(int &)': 
                     //             cannot convert argument 1 from 'int' to 'int &')

    do_smth_rvr(ReferenceType(+data, 3)); // that's eaten by MSVC and gcc
    do_other_rvr(10);// that's eaten by MSVC and gcc
                     // so temporary is implicitly created there

    return data[0];
}

См. также: Пример (by Jason Turner): https://www.youtube.com/watch?v=SnTV5BU9x6k

Пример горького опыта близкий к моему: https://forum.kde.org/viewtopic.php?f=74&t=88577

Предлагается позволить указанному расширению MSVC войти в новый стандарт.

З.Ы. Какие обходы для текущей стуации? 1) Перегружать функцию принающую rv-reference. Не хочется по причине разрастания boilerplate. К тому же если, аргументов больше одного (N), то придётся писать 2^N вариантов. Совсем не хочется.

2) Также можно было бы передавать ReferenceType по значению. Но это увеличит число копирований. А ReferenceType всё таки может весить побольше.

3) Самый неправильный (и быстрый по написанию, и возможно по работе) workaround - это нагло врать компилятору. Прописывать аргумент как постоянную ссылку, а внутри приводить с const_cast к мутабельному состоянию. В примере выше можно и без оного - в лоб, ибо const int* позволит мутировать данные, на которые указывает. Однако если ReferenceType устроен сложнее (например, содержит данные защищённо и к данным имеет доступ только через non-const методы), то придётся делать const_cast.

apolukhin commented 3 years ago

yndx-antoshkka, 15 октября 2018, 17:44 У этого расширения очень большие проблемы:

void increment(long& foo) { foo += 1; }
int main() {
    int x = 123;
    increment(x);
    assert(x == 124); // Oops!
}

Сами инженеры из MSVC рекомендуют не пользоваться этой особенностью.

Даниил Милютин, 15 октября 2018, 18:19 yndx-antoshkka, Ваш контр-пример не скомпилируется MSVC

https://godbolt.org/z/TfNi7Q

void increment(long& foo) { foo += 1; }

struct X 
{
     X(long* x): i(x) {}
     long* i = nullptr;
};

void increment(X& foo) { *foo.i += 1; }

int main() {
    int x = 123;
    long ll = 123;
    //increment(x); // MSVC won't allow that
    // implicit type conversion in this case is forbidden!
    increment(ll); // fine
    increment(X(&ll)); // fine
    //assert(x == 124); // Oops!
    //assert(ll == 125); // Fine
    return ll;
}

Моё предложение касается случаев, когда при implicit конвертации не создаётся новых объектов на стеке. В вашем примере (если б это работало по логике полного расслабления требований) создастся временный объект типа long. И я такое также ни в коей мере не поощряю. То есть предложение звучит "как расслабить требования, но с ограничениями".

Ваше видение предложения изменилось после моих уточнений? :)

Andrey Davydov, 16 октября 2018, 12:58 Даниил Милютин, MSVC ведят себя еще более странно -- важно не только то, создается ли временный объект явно или нет, но и является ли он примитивным типом: к примеру, increment((long)x) тоже не скомпилируется.

Даниил Милютин, 17 октября 2018, 3:08 Andrey Davydov, Такое поведение компилятора MSVC меня лично тоже устривает. Собственно свои идеи и уочнения я изложил.

Жду от коллег по цеху выраженных мыслей по этому поводу. Если есть ещё логические доводы против, то мне интересно их воспринять.

webreh, 20 ноября 2018, 20:46 Преобразование prvalue of T в T& является предупреждением 4238: nonstandard extension used: class rvalue used as lvalue. Оно было нужно в С++03 и бесполезно с появлением форм auto&&.