минимальная и идеальная переадресация

Алгоритм min обычно выражается следующим образом:

template <typename T>
const T& min(const T& x, const T& y)
{
    return y < x ? y : x;
}

Однако это не позволяет использовать конструкции вида min(a, b) = 0. Вы можете добиться этого с помощью дополнительной перегрузки:

template <typename T>
T& min(T& x, T& y)
{
    return y < x ? y : x;
}

Что я хотел бы сделать, так это объединить эти две перегрузки с помощью идеальной переадресации:

template <typename T>
T&& min(T&& x, T&& y)
{
    return y < x ? std::forward<T>(y) : std::forward<T>(x);
}

Однако g++ 4.5.0 выдает предупреждение для min(2, 4) о том, что я возвращаю ссылку на временный объект. Я сделал что-то не так?


Хорошо, я понимаю. Проблема с условным оператором. В моем первом решении, если я вызываю min(2, 4), условный оператор видит значение x и, таким образом, переходит от переадресованного x к созданию временного объекта. Конечно, было бы опасно возвращать это по ссылке! Если я передам все выражение вместо x и y по отдельности, компилятор больше не будет жаловаться:

template <typename T>
T&& min(T&& x, T&& y)
{
    return std::forward<T>(y < x ? y : x);
}

Хорошо, я избавился от ссылок на арифметические типы :)

#include <type_traits>

template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
min(T x, T y)
{
    return y < x ? y : x;
}

template <typename T>
typename std::enable_if<!std::is_arithmetic<T>::value, T&&>::type
min(T&& x, T&& y)
{
    return std::forward<T>(y < x ? y : x);
}

person fredoverflow    schedule 22.06.2010    source источник
comment
Хм, мне кажется, что последний пример тоже создает временный файл в соответствии с FCD. Но это кажется странным: кажется, создаются временные объекты для привязки int&& к int xvalue?! Я думал, что xvalues ​​​​являются анонимными ссылками rvalue, которые могут быть связаны ссылками rvalue без создания временных? Похоже, то же самое происходит и с вашим исправленным кодом, не так ли? Тип возвращаемого значения — int&&, а возвращаемое выражение — значение x int. Почему компилятор больше не предупреждает об этом?   -  person Johannes Schaub - litb    schedule 24.06.2010
comment
Если я правильно прочитал 8.5.3, это говорит о том, что это создает временное целое при привязке ссылки к xvalue: int x = 0; int &&rx = (int&&)x; Аналогично при использовании std::move. Я думаю, это не может быть намерением.   -  person Johannes Schaub - litb    schedule 24.06.2010
comment
@Johannes: О какой части 8.5.3 вы конкретно говорите?   -  person fredoverflow    schedule 24.06.2010
comment
@Fred все правила :) На этот раз применяется самый последний пункт, который создает временное.   -  person Johannes Schaub - litb    schedule 24.06.2010
comment
@Johannes: В середине страницы 204 я нахожу В противном случае, если T2 является типом класса и [...] выражение инициализатора является rvalue [...], тогда ссылка привязана к выражению инициализатора rvalue. Здесь не нужны временные объекты.   -  person fredoverflow    schedule 24.06.2010
comment
int не является типом класса. Итак, для int создается временный. Я думаю, это дефект.   -  person Johannes Schaub - litb    schedule 24.06.2010
comment
@Johannes: Ах, я этого не видел. Да, мне кажется дефект.   -  person fredoverflow    schedule 24.06.2010


Ответы (2)


Мне кажется, вы пытаетесь упростить проблему. К сожалению, получить его полностью правильно, безусловно, нетривиально. Если вы еще не читали N2199 Сейчас самое время это сделать. Ссылки на Rvalue продолжают развиваться, поэтому его эталонная реализация min и max, вероятно, уже не совсем правильная, но, по крайней мере, она должна быть довольно приличной отправной точкой. Предупреждение: эталонная реализация намного сложнее, чем вам хотелось бы!

person Jerry Coffin    schedule 22.06.2010
comment
Похоже, это предложение было отклонено. Есть идеи, почему? - person jalf; 22.06.2010
comment
@jalf: я подозреваю, что люди взглянули на эталонную реализацию и убежали, крича от ужаса. :-) - person Jerry Coffin; 22.06.2010
comment
@jalf да, см. groups.google.de/group/comp .std.c++/browse_thread/thread/ - person Johannes Schaub - litb; 22.06.2010
comment
вы пытаетесь упростить проблему -› я предпочитаю упрощенные проблемы слишком сложным решениям :) - person fredoverflow; 23.06.2010
comment
@FredOverflow: не поймите меня неправильно - я думаю, почти все согласны с тем, что решение, представленное в N2199, было / является чрезмерно сложным. OTOH, это не слишком сложно просто из-за извращенности - хотя статья не была принята, проблема не изменилась, и в язык не было добавлено ничего, чтобы позволить гораздо более простому решению выполнять ту же задачу. По сути, каждое более простое решение будет иметь проблемы и недостатки. - person Jerry Coffin; 23.06.2010
comment
Согласен, но я не собирался писать самую общую функцию min. Все, что я хотел, это объединить две функции в моем посте с помощью идеальной переадресации, чтобы лучше понять технику. Может быть, я должен был быть более ясным в своем первоначальном посте о своих намерениях. - person fredoverflow; 23.06.2010

Вам не нужна идеальная переадресация, здесь вы хотите вернуть T& или const T& и никогда T&&. std::forward предназначен для передачи одного из ваших параметров в другую функцию, а не для возвращаемых значений.

Я думаю, что вы хотите:

template <typename T>
min(T&& x, T&& y) -> decltype(x)
{
    return y < x ? y : x;
}

РЕДАКТИРОВАТЬ, чтобы избежать проблемы с оборванной ссылкой:

template <typename T>
struct dedangle { typedef T type; }

template <typename T>
struct dedangle<const T&> { typedef T type; }

template <typename T, typename U>
min(T&& x, U&& y) -> dedangle<decltype(0?y:x)>::type
{
    return y < x ? y : x;
}

// dedangle is re-usable by max, etc, so its cost is amortized
person Ben Voigt    schedule 22.06.2010
comment
Я не совсем уверен, что это за тип decltype(x), но это либо T, что совсем не то, что мне нужно, либо T&&, что, по вашему мнению, мне не следует делать. Верно? - person fredoverflow; 22.06.2010
comment
Раздел 14.8.2.1 [temp.deduct.call] стандарта описывает преобразования, которые происходят с аргументами шаблонных функций. decltype(x) легко может быть int& или const int&, тогда как возвращаемый тип не подвергается этому процессу. Ознакомьтесь с примером № 3 в этом разделе стандарта. - person Ben Voigt; 22.06.2010
comment
Я не согласен, decltype(x) всегда совпадает с T&&. Вы можете убедиться сами, вставив строку static_assert(std::is_same<T&&, decltype(x)>::value, "ouch"); в шаблон функции min. - person fredoverflow; 23.06.2010
comment
Итак, поскольку преобразование выполняется для T, а не для аргумента T&& x, оно все-таки влияет на тип возвращаемого значения? Что, если вы продолжите и вернете T&&, но опустите std::forward? - person Ben Voigt; 23.06.2010
comment
Это не сработает, потому что если я вызову min(2, 4), то T будет int&&, а из-за правил свертывания ссылок T&& также будет int&&. Поэтому я бы попытался связать int&& с y < x ? y : x, которое является lvalue (поскольку и y, и x, как и любой формальный параметр, lvalue), но ссылки rvalue не привязываются к lvalue. - person fredoverflow; 23.06.2010
comment
Если вы вызываете min(rval, rval), то T — это нессылочный тип U (где U — это тип rvalue). Тип параметров будет U&&. Тип выражения x будет lvalue из U, но поскольку decltype(x) (x без скобок) получает тип сущности x вместо выражения x (для которого это даст U&), decltype будет U&& . Итак, min(2, 4) вернет int&&. Однако типом выражений y и x является lvalue int, которое не может быть привязано к int&&, и для возврата произойдет ошибка времени компиляции. Таким образом, ваше определение min неверно. - person Johannes Schaub - litb; 24.06.2010
comment
@Johannes: Ты обращаешься к Бену или ко мне? - person fredoverflow; 24.06.2010
comment
@Johannes: Поможет ли использование decltype((x)) или decltype(0?y:x)? - person Ben Voigt; 24.06.2010
comment
@Fred, я обращаюсь к тому, кто чувствует, что к нему обращаются. Я ни к кому конкретно не обращался. - person Johannes Schaub - litb; 24.06.2010
comment
@Ben, тогда тип возвращаемого значения будет U& для неконстантных gl- или prvalue и U const& для константных. Я не знаю, что вы хотите архивировать. На мой взгляд, min(1, 2) должен возвращать int вместо ссылки. Обратите внимание, что вы, как и версия @Fred, не компилирует int x = 0; min(1, x); из-за несогласованного вывода для T по обоим параметрам. - person Johannes Schaub - litb; 24.06.2010
comment
@Johannes: согласен, с арифметическими типами лучше было бы обращаться вообще без каких-либо ссылок, но мысленно избавляясь от шаблонов, я не вижу серьезных проблем с int&& min(int&& x, int&& y) { return std::forward<int>(y < x ? y : x); }, за исключением возможности сказать int&& x = min(1, 2);, где x - оборванный Справка. Но кроме того, решение прекрасно, не так ли? - person fredoverflow; 24.06.2010
comment
@Fred: Вызывает ли использование auto именно эту проблему с оборванной ссылкой? @Johannes: T& и const T& - это именно то, что Фред просил в вопросе. - person Ben Voigt; 24.06.2010
comment
@Ben: проблема с оборванной ссылкой вызвана возвратом ссылки, но я не вижу здесь способа обойти это. Традиционный C++98 min имеет точно такую ​​же проблему. - person fredoverflow; 24.06.2010
comment
@Fred: с помощью вспомогательного шаблона легко возвращать значения, когда входные данные являются константными ссылками, и ссылки, когда входные данные не являются константными ссылками. - person Ben Voigt; 24.06.2010
comment
@Ben: Я знаю, играю с этим прямо сейчас :) Кстати, вот почему я открыл тему на enable_if пару минут назад :) Я опубликую свой код здесь, как только разберусь. - person fredoverflow; 24.06.2010
comment
@Ben: О, ты имеешь в виду возвращаемые значения даже для типов классов. Нет, это слишком неэффективно для меня, я бы предпочел жить с проблемой висячих ссылок :) Лично я никогда не связывал rvalue с локальной ссылочной переменной... - person fredoverflow; 24.06.2010