Наличие функции принимает только неконстантные значения lvalue

У меня есть функция, которая сортирует два вектора с первым из них в качестве критерия упорядочения. Его подпись

template<typename A, typename B>
void sort(A&& X, B&& Y)
{
  ..
}

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

sort(vector<int>{ 2,1,3 }, vector<int>{ 3,1,2 });

где значение r будет впоследствии уничтожено (ерунда).

Явный запрос lvalue не работает, так как

template<typename A, typename B>
void sort(A& X, B& Y) ... // (*)

sort(vector<int>{2,1,3}, vector<int>{3,1,2});

по какой-то причине приведенное выше компилируется (я думал, что только const lvalues ​​можно привязывать к rvalue и продлевать их время жизни?).

Если я добавлю const к ссылке lvalue, функция больше не сможет изменять векторы и сортировать их.


Мои вопросы:

1) Почему в примере, отмеченном // (*), я могу привязать значение r к значению l, которое даже не равно const? Почему вместо этого что-то вроде int& r = 20; не разрешено? Какая разница?

2) Как я могу решить мою проблему, т.е. сделать так, чтобы функция принимала только lvalue, а не временные значения rvalue? (если это возможно, конечно)

Очевидно, мне разрешено использовать любую доступную версию C++.


person Dean    schedule 22.09.2015    source источник
comment
Я подозреваю компилятор Visual Studio...   -  person BeyelerStudios    schedule 22.09.2015
comment
Как намекает @BeyelerStudios, компилятор Visual Studio некоторое время поддерживал привязку временных объектов к неконстантным ссылкам (к сожалению).   -  person James Adkison    schedule 22.09.2015
comment
Однако он выдает предупреждения, по крайней мере, если вы используете достаточно высокий уровень предупреждения, что вам, вероятно, следует   -  person stijn    schedule 22.09.2015
comment
В частности, MSVC 2013 выдаст предупреждение о mysourcefile.cpp(10): предупреждение C4239: используется нестандартное расширение: «аргумент»: преобразование из «‹type1›» в «‹type2›» 1> Неконстантная ссылка может быть только привязывается к lvalue только на уровне предупреждения /W4 или выше.   -  person jaggedSpire    schedule 22.09.2015
comment
@jaggedSpire хорошая информация   -  person BeyelerStudios    schedule 22.09.2015
comment
Да, версия сообщества MSVC 2015 :(   -  person Dean    schedule 22.09.2015


Ответы (5)


Ответ таков: ваш компилятор неверен.

Проверьте gcc или clang или что-то подобное, и вы получите что-то вроде этого:

prog.cpp: в функции 'int main()': prog.cpp:9:45: ошибка: неверная инициализация неконстантной ссылки типа 'std::vector&' из rvalue типа сортировки 'std::vector' (вектор{2,1,3}, вектор{3,1,2}); ^ prog.cpp: 6: 6: примечание: инициализация аргумента 1 'void sort (A &, B &) [с A = std::vector; B = std::vector]' void sort(A&X, B&Y) { }

person BeyelerStudios    schedule 22.09.2015
comment
Почему компилятор не выводит тип A = const vector<int>? Я имею в виду, что я могу явно вызвать split<const vector<int>, const vector<int> >(vector<int>{2,1,3}, vector<int>{3,1,2}), и компилятор с радостью примет мои временные файлы. Единственная дальняя ссылка на это, которую я мог найти, это: если P является типом cv-qualifiers, cv-qualifiers верхнего уровня игнорируются для вывода. из en.cppreference.com/w/cpp/language/template_argument_deduction - person whY; 10.01.2018
comment
@whY, потому что для rvalue ссылка const не является точным соответствием для вывода шаблона. Вы можете определить const vector<int> a{2, 1, 3}, b{3, 1, 2};, тогда a, b являются значениями lvalue, и, таким образом, ссылка на константу будет точным совпадением, или создайте экземпляр шаблона самостоятельно, чтобы привязать значение r к ссылке на константу. Сравните stackoverflow.com/q/11931452 и stackoverflow .com/q/6075093 - person BeyelerStudios; 13.01.2018

Вы можете использовать параметр компилятора /Za, чтобы превратить это в ошибку:

error C2664: 'void sort<std::vector<int,std::allocator<_Ty>>,std::vector<_Ty,std::allocator<_Ty>>>(A &,B &)' : cannot convert argument 1
from 'std::vector<int,std::allocator<_Ty>>' to 'std::vector<int,std::allocator<_Ty>> &'
        with
        [
            _Ty=int
,            A=std::vector<int,std::allocator<int>>
,            B=std::vector<int,std::allocator<int>>
        ]
        and
        [
            _Ty=int
        ]
        and
        [
            _Ty=int
        ]
        A non-const reference may only be bound to an lvalue

Обратите внимание, что у /Za в прошлом были некоторые проблемы, и даже сейчас он все еще ломает <windows.h>, поэтому вы все равно не можете использовать его для всех единиц компиляции. В публикации 2012 года под названием "MSVC /Za считается вредным ", старший инженер Microsoft Стефан Т. Лававей даже рекомендует не использовать этот флаг, но вам также следует ознакомиться с комментариями по адресу Исправления STL в VS 2015, часть 2, где он говорит:

У нас определенно были встречи по поводу вариантов соответствия /Za и /Zc. В конечном итоге мы хотим достичь точки, в которой VC будет соответствовать стандарту по умолчанию, без необходимости запрашивать дополнительные параметры, чтобы этот путь стал наиболее часто используемым и проверенным путем. Как вы можете видеть в посте, я работал над этим в STL, удаляя нестандартные механизмы, когда это было возможно.

Так что, скорее всего, это будет ошибка компиляции по умолчанию в какой-то будущей версии MSVC.


Еще одна вещь: стандарт С++ не различает ошибки и предупреждения, он говорит только о «диагностических сообщениях». Это означает, что MSVC фактически соответствует требованиям, как только выдает предупреждение.

person Christian Hackl    schedule 22.09.2015
comment
Хотелось бы, чтобы STL перестала называть это STL - person Lightness Races in Orbit; 22.09.2015
comment
@LightnessRacesinOrbit: Я знаю, я знаю... с другой стороны, с таким именем я бы сказал, что он будет прощен. - person Christian Hackl; 22.09.2015
comment
@LightnessRacesinOrbit кажется, что Херб и Бьярне официально называют стандартную библиотеку STL (см. ">репозиторий cppcore). Так что я полагаю, что теперь STL = STandard Library - person Dean; 22.09.2015
comment
@Dean: Ни Херб, ни Бьярне не имеют права определять официальное наименование. Официальным можно считать только то, что указано в стандартном документе ISO. - person Christian Hackl; 22.09.2015
comment
@ChristianHackl: я бы возразил, что еще хуже исходить от кого-то с таким же именем :P - person Lightness Races in Orbit; 22.09.2015

Как отмечается в других ответах, компилятор ошибается.

Без необходимости менять компилятор параметров компилятора:

struct sfinae_helper {};
template<bool b>
using sfinae = typename std::enable_if<b, sfinae_helper>::type*;
// sfinae_helper, because standard is dumb: void*s are illegal here

template<class A, class B,
  sfinae<!std::is_const<A>::value&&!std::is_const<B>::value> = nullptr
>
void sort(A& X, B& Y) ... // (*)

sort(vector<int>{2,1,3}, vector<int>{3,1,2});

также не сможет скомпилироваться в MSVC2013 и должен быть совместим с совместимыми компиляторами.

Обратите внимание, что выведение A и B как const X недопустимо по стандарту, но явная передача const X как A или B допустима.

Окончательный подход:

template<typename A, typename B>
void sort(A& X, B& Y) ... // (*)
template<typename A, typename B>
void sort(A&& X, B&& Y) = delete;

где мы генерируем явно удаленный файл, который следует предпочесть A&, B&. Я не знаю, правильно ли MSVC выбирает в этом случае идеально перенаправленный, но я надеюсь на это.

person Yakk - Adam Nevraumont    schedule 22.09.2015
comment
Пример sfinae_helper компилируется без ошибок и предупреждений в MSVC 2013 (если не используется /W4 или /Za). - person Christian Hackl; 22.09.2015
comment
Да вообще ошибки нет. Если только я что-то не напутал, когда превратил его в полноценный фрагмент кода (т. е. поместил вызов в main, добавил <vector> и <type_traits> включает). - person Christian Hackl; 22.09.2015
comment
@chris Интересно, происходит ли привязка rvalue к lvalue, и я неправильно понял это как проблему вывода типа. - person Yakk - Adam Nevraumont; 22.09.2015

В качестве ответа на проблему X, которую вы пытаетесь решить, а не на проблему Y, которую вы задали... правильный ответ заключается в том, что вы не должны делать то, что пытаетесь сделать. Неспособность представить, как что-то может быть полезным, не является достаточной причиной, чтобы изо всех сил пытаться помешать людям сделать это.

И, на самом деле, мне даже не нужно предлагать это абстрактно: вот два конкретных примера, где было бы полезно принять временный объект.

Вы можете заботиться только об одном из двух объектов:

interesting_container A;
// fill A
sort(an_ordering_criterion(), A);

Контейнеры не являются «автономными»; например контейнер, который обеспечивает представление другого:

vector<int> A, B;
// fill A and B
sort(make_subsequence(A, 1, 10), make_subsequence(B, 5, 14));
person Community    schedule 23.09.2015

Вы можете явно delete нежелательно перегружать sort функцию:

#include <iostream>
#include <vector>

#include <cstdlib>

template< typename X, typename Y >
void
sort(X &, Y &)
{
    static_assert(!std::is_const< X >{});
    static_assert(!std::is_const< Y >{});
}

template< typename X, typename Y >
int
sort(X const &, Y &) = delete;

template< typename X, typename Y >
int
sort(X &, Y const &) = delete;

template< typename X, typename Y >
int
sort(X const &, Y const &) = delete;

int
main()
{
    std::vector< int > v{1, 3, 5};
    std::vector< int > const c{2, 4, 6};
    ::sort(v, v); // valid
    { // has been explicitly deleted
        //::sort(v, c); 
        //::sort(c, v);
        //::sort(c, c);
    }
    { // not viable: expects an l-value for 1st argument
        //::sort(std::move(v), v); 
        //::sort(std::move(v), c);
        //::sort(std::move(c), v);
        //::sort(std::move(c), c);
    }
    { // not viable: expects an l-value for 2nd argument
        //::sort(v, std::move(v));
        //::sort(v, std::move(c));
        //::sort(c, std::move(v));
        //::sort(c, std::move(c));
    }
    { // not viable: expects an l-value for 1st or 2nd argument
        //::sort(std::move(v), std::move(v));
        //::sort(std::move(v), std::move(c));
        //::sort(std::move(c), std::move(v));
        //::sort(std::move(c), std::move(c));
    }
    return EXIT_SUCCESS;
}
person Tomilov Anatoliy    schedule 23.09.2015