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

Передача неконстантных ссылок на rvalues ​​в С++

В следующей строке кода:

bootrec_reset(File(path, size, off), blksize);

Вызов функции с прототипом:

static void bootrec_reset(File &file, ssize_t blksize);

Я получаю эту ошибку:

libcpfs/mkfs.cc: 99: 53: ошибка: недействительная инициализация неконстантной ссылки типа 'File &' из rvalue типа 'File'

libcpfs/mkfs.cc: 30: 13: ошибка: при передаче аргумента 1 из 'void bootrec_reset (File &, ssize_t)'

Я знаю, что вы не можете передавать неконстантные ссылки (const &) на rvalues ​​в соответствии со стандартом. Однако MSVC позволяет это сделать (см. этот вопрос). Этот вопрос пытается объяснить, почему, но ответ не имеет смысла, поскольку он использует ссылки на литералы, которые являются краеугольным камнем и, очевидно, должны быть запрещены.

В данном примере ясно видно, что произойдет следующий порядок событий (как в MSVC):

  • Вызов конструктора
  • File.
  • Ссылка на File и blksize помещается в стек.
  • bootrec_reset использует File.
  • После возврата из bootrec_reset временный File будет уничтожен.

Необходимо указать, что ссылка File должна быть неконстантной, так как она является временным дескриптором файла, на который вызывается неконтентные методы. Кроме того, я не хочу передавать аргументы конструктора File в bootrec_reset, которые должны быть построены там, и я не вижу причин вручную создавать и уничтожать объект File в вызывающем.

Итак, мои вопросы:

  • Что оправдывает стандарт С++, запрещающий неконстантные ссылки таким образом?
  • Как я могу заставить GCC разрешить этот код?
  • Неужели следующий стандарт С++ 0x изменит это в любом случае, или есть ли что-то, что новый стандарт дает мне, что более уместно здесь, например, все, что смешно о ссылках на rvalue?
4b9b3361

Ответ 1

Да, тот факт, что простые функции не могут связывать неконстантные ссылки на временные, но методы могут - всегда меня били. TTBOMK: логика выглядит примерно так (из этот comp.lang.С++. Модерируемый поток):

Предположим, что у вас есть:

 void inc( long &x ) { ++x; }

 void test() {
     int y = 0;
     inc( y );
     std::cout << y;
 } 

Если вы допустили, что параметр long &x inc() привязан к временной копии long, сделанной из y, этот код, очевидно, не будет делать то, что вы ожидаете, - компилятор просто молчал бы код, который оставляет y неизменным. По-видимому, это был общий источник ошибок в ранние дни С++.

Если бы я разработал С++, я бы предпочел бы, чтобы ссылки, не связанные с константой, привязывались к временным, но запрещали автоматические преобразования от lvalues ​​к временным связям при привязке к ссылкам. Но кто знает, что, возможно, открыл бы другую банку червей...

Ответ 2

  • "Что оправдывает стандарт С++, запрещающий неконстантные ссылки таким образом?"

Практический опыт с противоположным соглашением, каким было то, как изначально работали. С++ - это в значительной степени развитый язык, а не разработанный. Во многом, все еще существуют правила, которые оказались в работе (хотя некоторые исключения BIG произошли с стандартизацией 1998 года, например, печально известная export, где комитет изобрел, а не стандартизовал существующую практику).

Для правила привязки имелся не только опыт в С++, но и аналогичный опыт с другими языками, такими как Fortran.

Как отмечает @j_random_hacker в своем ответе (который, как я писал, был забито 0, показывая, что оценка в SO действительно не работает как показатель качества), самые серьезные проблемы связаны с неявными преобразованиями и перегрузкой разрешение.

  • "Как я могу заставить GCC разрешить этот код?"

Вы не можете.

Вместо...

bootrec_reset(File(path, size, off), blksize);

... написать...

File f(path, size, off);
bootrec_reset(f, blksize);

Или определите соответствующую перегрузку bootrec_reset. Или, если "умные" призывы кода, вы можете в принципе написать bootrec_reset(tempref(File(path, size, off)), blksize);, где вы просто определяете tempref, чтобы вернуть свою аргументную ссылку соответствующим образом const-casted. Но даже если это техническое решение, не делайте этого.

  • "Неужели следующий стандарт С++ 0x в любом случае изменит его или есть что-то, что новый стандарт дает мне, что более уместно здесь, например, все, что смешно о ссылках на rvalue?"

Нет, ничего, что меняет вещи для данного кода.

Если вы хотите переписать, вы можете использовать, например. С++ 0x rvalue, или обходные решения С++ 98, показанные выше.

Приветствия и hth.,

Ответ 3

В любом случае, изменится ли этот новый стандарт С++ 0x, или есть что-то, что новый стандарт дает мне, что более уместно здесь, например, все, что смешно о ссылках rvalue?

Да. Поскольку каждое имя является lvalue, почти тривиально обращаться с любым выражением, как если бы оно было lvalue:

template <typename T>
T& as_lvalue(T&& x)
{
    return x;
}

// ...

bootrec_reset(as_lvalue(File(path, size, off)), blksize);

Ответ 4

  • Является довольно произвольным решением - неконстантные ссылки на временные файлы разрешены, например, когда временный объект является объектом вызова метода (например, "своп-трюк" для освобождения памяти, выделенной вектором, std::vector<type>().swap(some_vector);)
  • Короче говоря временное имя, я не думаю, что вы можете.
  • Насколько я знаю, это правило существует и в С++ 0x (для регулярных ссылок), но ссылки rvalue специально существуют, поэтому вы можете привязывать ссылки на временные разделы - поэтому изменение bootrec_reset для принятия File && должно сделать правовой код.

Ответ 5

Обратите внимание, что вызов С++ 0x "jibberish" не дает очень благоприятной картины вашей способности кодирования или желания понять язык.

1) На самом деле это не так произвольно. Разрешение неконстантных ссылок на привязку к r-значениям приводит к чрезвычайно запутанному коду. Недавно я зарегистрировал ошибку в отношении MSVC, которая относится к этому, где нестандартное поведение приводило к тому, что стандартный код не смог скомпилировать и/или скомпилировать с отклоняющимся поведением.

В вашем случае рассмотрите:

#include <iostream>

template<typename T>
void func(T& t)
{
    int& r = t;
    ++r;
}

int main(void)
{
    int i = 4;
    long n = 5;
    const int& r = n;

    const int ci = 6;
    const long cn = 7;

    //int& r1 = ci;
    //int& r2 = cn;

    func(i);
    //func(n);

    std::cout << r << std::endl;
}

Какую из прокомментированных строк вы хотите скомпилировать? Вы хотите, чтобы func(i) изменил свой аргумент и func(n), чтобы НЕ делать это?

2) Вы не можете скомпилировать этот код. Вы не хотите иметь этот код. Будущая версия MSVC, вероятно, собирается удалить нестандартное расширение и не сможет скомпилировать этот код. Вместо этого используйте локальную переменную. Вы всегда можете использовать дополнительную пару фигурных скобок для управления временем жизни этой локальной переменной и заставить ее уничтожаться до следующей строки кода, как и временная. Или ссылки r-значения.

{
  File ftemp(path, size, off);
  bootrec_reset(ftemp, blksize);
}

3) Да, в этом сценарии вы можете использовать ссылки r-значения С++ 0x.

Ответ 6

Альтернативно просто перегрузите.

static void bootrec_reset(File &&file, ssize_t blksize) {
    return bootrec_reset(file, blksize);
}

Это самое простое решение.

Ответ 7

Как я могу заставить GCC разрешить этот код?

Если у вас есть определение файла, вы можете попробовать сыграть трюки, такие как этот:

class File /* ... */ {
public:
  File* operator&() { return this; }
/* ... */
};
/* ... */
bootrec_reset(*&File(path, size, off), blksize);

Это компилируется для меня в режиме С++ 98.

В любом случае, изменится ли этот новый стандарт С++ 0x, или есть что-то, что новый стандарт дает мне, что более уместно здесь, например, все, что смешно о ссылках rvalue?

Очевидно, что это путь, если это вообще возможно.