Почему этот "минимальный" шаблон cpp-next по ошибке?
Я читал cpp-next, где этот шаблон min
представлен в качестве примера того, как подробный код на С++ можно сравнить с кодом python
template <class T, class U>
auto min(T x, U y)->decltype(x < y ? x : y)
{ return x < y ? x : y; }
Сначала это выглядит невинно, но Дэйвед Вандевоорд сделал это замечание
Шаблон min, который использует decltype в спецификации возвращаемого типа, не работает: он возвращает ссылку (потому что аргумент является lvalue), который заканчивается ссылкой на локальную переменную в большинстве распространенных применений.
Я понял, что может быть неясно всем, как проявляется проблема. Не могли бы вы дать подробное объяснение и возможные исправления?
Ответы
Ответ 1
Проблема заключается в том, что аргументы не рассматриваются как ссылки. Это вызывает нарезку, в случае полиморфных типов, а затем ссылку возвращается к локальной переменной. Решение состоит в том, чтобы принимать аргументы как ссылки rvalue, вызывать идеальную пересылку, а затем просто выводить и возвращать возвращаемый тип. Когда это будет сделано, возврат ссылки будет прекрасным, поскольку значение все еще существует.
Ответ 2
rev 3: KonradRudolph
template <class T, class U>
auto min(T x, U y) -> typename std::remove_reference<decltype(x < y ? x : y)>::type
{
return x < y ? x : y;
}
rev 2: KennyTM
template <class T, class U>
auto min(T x, U y)->decltype(x < y ? std::declval<T>() : std::declval<U>())
{
return x < y ? x : y;
}
rev 1: T и U должны быть по умолчанию конструктивными
template <class T, class U>
auto min(T x, U y)->decltype(x < y ? T() : U())
{
return x < y ? x : y;
}
Тест:
int main()
{
int x; int y;
static_assert(std::is_same<decltype(min(x, y)), int>::value, "");
return 0;
}
EDIT:
Я немного удивлен, но он действительно компилируется с помощью remove_reference.
Ответ 3
Аргументы передаются по значению (T
и U
, выведенному как int
), но тип выражения ?:
выводится как ссылка в этом случае, так как они являются локальными значениями внутри функции. Специфика будет в ответе @Johannes, который должен прийти через несколько минут.: D
Ответ 4
Какая все суета, и почему никто не пытается найти очевидное решение, которое идеально пересылает?
template <class T, class U>
typename std::enable_if< ! std::is_integral< T >() || ! std::is_integral< U >(),
typename std::common_type< T, U >::type >::type
min(T &&x, U &&y)
{ return x < y ? std::forward< T >( x ) : std::forward< U >( y ); }
template <class T, class U>
decltype( typename std::enable_if< std::is_integral< T >() && std::is_integral< U >(),
decltype( typename std::common_type< T, U >
::type{ U( -1 ) } ) >::type{ T( -1 ) } )
min(T &&x, U &&y)
{ return x < y ? std::forward< T >( x ) : std::forward< U >( y ); }
Теперь он работает так же, как если бы вы помещали выражение в вызывающую функцию, что именно то, что пользователь ожидает (и просто лучшее в целом).
Изменить: Теперь он запрещает опасные неподписанные или подписанные операции на бумаге Говарда, требуя, чтобы преобразование из каждого типа операнда в тип результата не суживалось, если оба операнда имеют целостный тип, Однако GCC не будет компилировать это, жалуясь "sorry, unimplemented: mangling constructor
". Это происходит, если в сигнатуре функции используется любая формальная инициализация.
Ответ 5
Возвращение по ссылке иногда может быть функцией, а не ошибкой. Мы вернемся к этому позже. Сначала рассмотрим основы:
int x; int y;
x // this is an lvalue
y // lvalue also
x+y // not an lvalue - you couldn't do (x+y) = 3
x<y?x:y // lvalue - you can do (x<y?x:y) = 0
Последняя строка показывает, что a ?:
часто может быть lvalue. т.е. вы можете сделать (x<y?x:y)=0
, чтобы установить наименьшую переменную в 0 и оставить другую. Конечно, вы не можете сделать (1<3?6:8)=0
, как вы не можете сделать 6=0
или 8=0
. Так что это просто rvalue в этом случае.
Внутри min, x и y - имена параметров функции и, следовательно, lvalues. decltype(x<y?:x:y)
int&
. (Я нашел эту другую статью cpp-Next полезную.)
Так почему это может быть проблемой? Ну, если возвращаемый тип min
является ссылкой, тогда он вернет ссылку на один из x
или y
, параметры функции. Вопрос в том, были ли х и у ссылки сами?
Рассмотрим этот прецедент:
int m = 5; int n = 10;
min(m,n) = 0; // do you want this to work?
У нас есть решение сделать. Может быть, мы хотим, чтобы min возвращал ссылки, если аргументы min
были ссылками. Я думаю, это несколько деликатный вопрос. Если вы строго хотите вернуть только не ссылки, это легко обеспечить с помощью std::remove_reference
вокруг decltype(x<y?x:y)
. Но это скучно. Позвольте себе позволить (иногда) возвращать ссылки; он может быть более эффективным и полезным во многих случаях.
Если вы используете исходное определение примера min
, а также не ссылочные типы для x или y, то min вернет ссылку на локальные значения среди своих параметров. Это плохо, поскольку ссылки будут недействительными и поведение undefined. Например, это было бы плохо:
int p = min(5,8); // reading from a now-invalid reference.
Итак, нам нужно пройти через множество вариантов использования и решить, какое поведение мы хотим:
// Desired behaviour
int m = 5;
int n = 10;
min(3,7); // return by value. i.e. return an int
min(m,n); // return an int& which maps to either m or n
min(3,n); // return by value
min(foo(), bar()) // what makes sense here?
Можем ли мы согласиться с тем, какое поведение мы хотели бы получить от такого min
? И тогда, как мы его реализуем?