Это следующий вопрос: С++ 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, используемом в MSVC Intellisense).
Как насчет этого кода?
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 от привязки к аргументам refvalue. Я понимаю обоснование этого - объясняется в N2831 "Устранение проблемы безопасности с помощью ссылок rvalue". Я также думаю, что GCC, вероятно, реализует этот пункт, как это было предусмотрено авторами этой статьи, потому что исходный патч для GCC был написан одним из авторов (Doug Gregor).
Однако я не уверен в этом. Для меня (a) a const string &
концептуально ближе к string &&
, чем a const char *
, и (b) компилятор может создать временную строку в g2
, как если бы она была написана следующим образом:
void g2(const std::string & arg)
{
f(std::string(arg));
}
Действительно, иногда конструктор копирования считается неявным оператором преобразования. Синтаксически это предлагается формой конструктора копирования, и стандарт даже упоминает это конкретно в разделе 13.3.3.1.2/4, где конструктор копирования для преобразований с производной базой получает более высокий ранг преобразования, чем другие определяемые пользователем преобразования:
Преобразование выражения типа класса в один и тот же тип класса дано в виде Точного совпадения, а преобразование выражения типа класса к базовому классу этого типа присваивается ранг конверсии, несмотря на то, что в этих случаях вызывается конструктор копирования/перемещения (т.е. пользовательская функция преобразования).
(Я предполагаю, что это используется при передаче производного класса на функцию типа void h(Base)
, которая берет базовый класс по значению.)
Мотивация
Моя мотивация для запроса - это что-то вроде вопроса, заданного в Как уменьшить избыточный код при добавлении новых перегрузок ссылок С++ 0x rvalue ( "Как уменьшить избыточный код при добавлении новых перегрузок ссылок на С++ 0x rvalue" ).
Если у вас есть функция, которая принимает несколько потенциально подвижных аргументов и будет перемещать их, если это возможно (например, factory function/constructor: Object create_object(string, vector<string>, string)
или тому подобное), и вы хотите переместить или скопировать каждый аргумент, если нужно, вы быстро начнете писать много кода.
Если типы аргументов являются подвижными, тогда можно просто написать одну версию, которая принимает аргументы по значению, как указано выше. Но если аргументы являются (устаревшими) классами non-movable-but-swappable a la С++ 03, и вы не можете их изменить, то более эффективная запись ссылочных перегрузок rvalue более эффективна.
Итак, если lvalues связывается с rvalues через неявную копию, тогда вы можете написать только одну перегрузку, например create_object(legacy_string &&, legacy_vector<legacy_string> &&, legacy_string &&)
, и она будет более или менее работать как предоставление всех комбинаций ссылочных перегрузок rvalue/lvalue - фактические аргументы, которые были lvalues будут скопированы, а затем привязаны к аргументам, фактические аргументы, которые были rvalues, будут напрямую связаны.
Разъяснение/редактирование: Я понимаю, что это практически идентично принятию аргументов по значению для подвижных типов, таких как С++ 0x std::string и std::vector (за исключением количества перемещений конструктор концептуально вызывается). Однако он не идентичен для копируемых, но недвижущихся типов, который включает в себя все классы С++ 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
аргументы. Я не вижу, как они эквивалентны.
Вопросы
Мои вопросы:
- Является ли это допустимой интерпретацией стандарта? Похоже, что это не обычный или предполагаемый, во всяком случае.
- Есть ли у него интуитивный смысл?
- Есть ли проблема с этой идеей, которую я не вижу? Кажется, что вы могли бы получать копии, создаваемые тихо, когда это не совсем ожидалось, но статус-кво в местах на С++ 03 в любом случае. сделало бы некоторые перегрузки жизнеспособными, когда они в настоящее время нет, но я не вижу, что это проблема на практике.
- Является ли это достаточно значительным улучшением, которое стоило бы сделать, например, экспериментальный патч для GCC?