Это дополнительный вопрос к
C ++ 0x rvalue ссылки и временные ссылки
В предыдущем вопросе я спросил, как должен работать этот код:
void f(const std::string &); //less efficient
void f(std::string &&); //more efficient
void g(const char * arg)
{
f(arg);
}
Кажется, что перегрузка перемещения, вероятно, должна вызываться из-за неявного временного, и это происходит в GCC, но не в MSVC (или в интерфейсе EDG, используемом в IntelliSense MSVC).
А как насчет этого кода?
void f(std::string &&); //NB: No const string & overload supplied
void g1(const char * arg)
{
f(arg);
}
void g2(const std::string & arg)
{
f(arg);
}
Похоже, что, основываясь на ответах на мой предыдущий вопрос, функция g1
является законной (и принята GCC 4.3-4.5, но не MSVC). Однако GCC и MSVC оба отклоняют g2
из-за пункта 13.3.3.1.4 / 3, который запрещает привязке lvalues к аргументам rvalue ref. Я понимаю причину этого - это объясняется в N2831 «Устранение проблемы безопасности с помощью ссылок на значения». Я также думаю, что GCC, вероятно, реализует этот пункт, как задумано авторами этого документа, потому что оригинальный патч для GCC был написан одним из авторов (Даг Грегор).
Однако я не думаю, что это довольно интуитивно понятно. Для меня (а) const string &
концептуально ближе к string &&
, чем const char *
, и (б) компилятор может создать временную строку в g2
, как если бы она была написана так:
void g2(const std::string & arg)
{
f(std::string(arg));
}
Действительно, иногда конструктор копирования считается оператором неявного преобразования. Синтаксически это предлагается формой конструктора копирования, и стандарт даже упоминает об этом конкретно в пункте 13.3.3.1.2 / 4, где конструктору копирования для преобразований производной базы присваивается более высокий ранг преобразования, чем в других определенных пользователем преобразования:
Преобразованию выражения типа класса в тот же тип класса присваивается ранг Точное совпадение, и преобразование
выражения типа класса к базовому классу этого типа присваивается рейтинг преобразования, несмотря на то, что
для этих случаев вызывается конструктор копирования / перемещения (т. е. определяемая пользователем функция преобразования).
(Я предполагаю, что это используется при передаче производного класса в функцию, подобную void h(Base)
, которая принимает базовый класс по значению.)
Мотивация
Моя мотивация задать этот вопрос похожа на вопрос, заданный в Как уменьшить избыточный код при добавлении новых перегруженных ссылочных операторов c ++ 0x rvalue ("Как уменьшить избыточный код при добавлении нового c ++ 0x rvalue ссылочный оператор перегрузки ").
Если у вас есть функция, которая принимает несколько потенциально перемещаемых аргументов и будет перемещать их, если это возможно (например, фабричная функция / конструктор: Object create_object(string, vector<string>, string)
или подобное), и вы хотите переместить или скопировать каждый аргумент как уместно, вы быстро начинаете писать lot кода.
Если типы аргументов являются подвижными, то можно просто написать одну версию, которая принимает аргументы по значению, как указано выше. Но если аргументы являются (устаревшими) классами, не являющимися перемещаемыми, но заменяемыми, как C ++ 03, и вы не можете их изменить, то запись перегруженных ссылок на rvalue более эффективна.
Таким образом, если lvalues действительно связывался с rvalues через неявную копию, то вы могли бы написать только одну перегрузку, как create_object(legacy_string &&, legacy_vector<legacy_string> &&, legacy_string &&)
, и это более или менее работало бы как предоставление всех комбинаций ссылочных перегрузок rvalue / lvalue - реальных аргументов, которые были lvalues будет скопирован, а затем привязан к аргументам, фактические аргументы, являющиеся значениями, будут напрямую связаны.
Пояснение / редактирование: Я понимаю, что это практически идентично принятию аргументов по значению для подвижных типов, таких как C ++ 0x std :: string и std :: vector (за исключением числа раз конструктор перемещения вызывается концептуально). Однако он не идентичен для копируемых, но неподвижных типов, который включает все классы C ++ 03 с явно определенными конструкторами копирования. Рассмотрим этот пример:
class legacy_string { legacy_string(const legacy_string &); }; //defined in a header somewhere; not modifiable.
void f(legacy_string s1, legacy_string s2); //A *new* (C++0x) function that wants to move from its arguments where possible, and avoid copying
void g() //A C++0x function as well
{
legacy_string x(/*initialization*/);
legacy_string y(/*initialization*/);
f(std::move(x), std::move(y));
}
Если g
вызывает f
, то x
и y
будут скопированы - я не вижу, как компилятор может их перемещать. Если бы вместо этого f
были объявлены как принимающие legacy_string &&
аргументов, он мог бы избежать тех копий, когда вызывающий явно вызывал std::move
для аргументов. Я не вижу, как они эквивалентны.
Вопросы
Мои вопросы:
- Является ли это действительной интерпретацией стандарта? Похоже, что это не обычный или предполагаемый, во всяком случае.
- Имеет ли это интуитивный смысл?
- Есть ли проблема с этой идеей, которую я не вижу? Похоже, что вы можете спокойно создавать копии, когда это не совсем ожидаемо, но в любом случае это статус-кво в местах в C ++ 03. сделало бы некоторые перегрузки жизнеспособными, когда их в данный момент нет, но я не вижу в этом проблемы на практике.
- Является ли это достаточно значительным улучшением, которое стоило бы сделать, например, экспериментальный патч для GCC?