Подтвердить что ты не робот

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

Я понял, что может быть неясно всем, как проявляется проблема. Не могли бы вы дать подробное объяснение и возможные исправления?

4b9b3361

Ответ 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? И тогда, как мы его реализуем?