Проходя неконстантной ссылки на правосторонние значения в C++

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

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)'

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

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

  1. будет быть призванным.
  2. ссылка File и blksize, передаются в стеке.
  3. bootrec_reset использует file.
  4. после возвращения из bootrec_reset, временно File разрушается.

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

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

  1. что оправдывает стандарт C++, запрещающий ссылки non-const таким образом?
  2. как я могу заставить GCC разрешить этот код?
  3. делает ли предстоящий стандарт C++0x изменить это в любом случае, или есть что-то новый стандарт дает мне, что более уместно здесь, например вся эта болтовня о ссылках rvalue?

7 ответов


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

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

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

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

если вы позволили


  • " что оправдывает стандарт C++, запрещающий ссылки non-const таким образом?"

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

для правила привязки был не только опыт работы в C++, но и аналогичный опыт работы с другими языками, такими как 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. Но даже если это техническое решение, не надо.--11-->

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

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

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

Cheers & hth.,


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

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

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

// ...

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

  1. является довольно произвольным решением-non-const ссылки на временные допускаются, когда Временное является предметом вызова метода, например (например, "трюк подкачки", чтобы освободить память, выделенную вектором,std::vector<type>().swap(some_vector);)
  2. давали временное имя, я не думаю, что вы можете.
  3. насколько мне известно, это правило существует и в C++0x (для регулярных ссылок), но ссылки rvalue специально существуют, поэтому вы можете привязать ссылки к временным-так изменение bootrec_reset принять File && должен сделать код законным.

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

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

In ваш случай, рассмотрим:

#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-значения C++0x в этом сценарии.


кроме того, просто перегрузка.

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

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


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

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

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

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

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

очевидно это так держать, если это вообще возможно.