Передача неконстантных ссылок на значения в C ++ - PullRequest
13 голосов
/ 03 ноября 2010

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

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

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

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

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

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

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

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

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

  1. File.
  2. Ссылка на File и blksize помещается в стек.
  3. bootrec_reset использует file.
  4. После возвращения с bootrec_reset временный File уничтожается.

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

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

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

Ответы [ 7 ]

8 голосов
/ 03 ноября 2010

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

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

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

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

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

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

6 голосов
/ 03 ноября 2010

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

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

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

// ...

bootrec_reset(as_lvalue(File(path, size, off)), blksize);
6 голосов
/ 03 ноября 2010
  • "Что оправдывает стандарт C ++, запрещающий неконстантные ссылки таким образом?"

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

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

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

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

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

2 голосов
/ 03 ноября 2010
  1. Это довольно произвольное решение - неконстантные ссылки на временные ссылки допускаются, например, когда временным является предмет вызова метода (например, «трюк подкачки» для освобождения памяти, выделенной вектором, std::vector<type>().swap(some_vector);)
  2. Если не считать временное имя, я не думаю, что вы можете.
  3. Насколько я знаю, это правило существует и в C ++ 0x (для регулярных ссылок), но ссылки на rvalue существуют специально, так что вы можете привязывать ссылки к временным файлам - поэтому изменение bootrec_reset на File && должно сделать код юридический.
1 голос
/ 07 октября 2012

Или просто перегрузить.

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

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

1 голос
/ 03 ноября 2010

Обратите внимание, что называя C ++ 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) Да, в этом сценарии вы можете использовать ссылки на значения C ++ 0x.

0 голосов
/ 11 июля 2015

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

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

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

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

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

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

...