Шаблон принимает const, но не буквальный
При написании шаблона, class T
может быть заменен const
тип.
Рассматривать:
template<class T> T& min(T& a, T& b) {
return a < b ? a : b;
}
Это будет работать в следующих случаях:
int a = 1, b = 5;
const int c = 1, d = 5;
min(a, b); // T is int
min(c, d); // T is const int
Но при вызове с литералом выдает ошибку компиляции:
min(1, 5); // T is const int literal
неверная инициализация неконстантной ссылки типа 'int &' из значения типа 'int'
Зачем? Разве не является буквальным const int
? И как можно изменить шаблон, чтобы разрешить работу с литералами?
(в соответствии с gcc 6.3 и MSVC 2015)
3 ответа
int
литералы имеют тип int
не const int
, T
Поэтому выводится int
, а также int&
не может связываться с prvalue.
Правильный способ написания такой функции - либо усовершенствовать аргументы, либо использовать const T&
и то, и другое может связываться с чем угодно.
template<typename T, typename U>
auto min(T&& a, U&& b) -> decltype(a < b ? std::forward<T>(a) : std::forward<U>(b))
{
return a < b ? std::forward<T>(a) : std::forward<U>(b);
}
// Or...
template<typename T>
const T& min(const T& a, const T& b)
{
return a < b ? a : b;
}
В случае идеальной пересылки аргументов, два параметра шаблона необходимы для того, чтобы int a{}; min(a, 42);
компилировать, так как их выведенные типы различны.
Разве int не является константой int?
Нет, это просто int
не const
, и определяется как prvalue, следовательно, ссылка lvalue не может привязываться к нему - как в вашем случае.
Легко исправляется, если исходный шаблон выглядит так:
template<typename T>
const T& min(const T& a, const T& b){
return a < b ? a : b;
}
как const T&
будет также привязываться к rvalue s.
Избегайте изменения или добавления чего-либо подобного этому:
template<typename T, typename U>
auto&& min(T&& a, U&& b){
return std::forward<T>(a < b ? a : b);
}
как здесь, мы не создаем копию из материализованного временного, и как таковой мы рискуем возвратить висячую ссылку. Смотрите здесь в [class.tevent]:
Временный объект, связанный со ссылочным параметром в вызове функции ([expr.call]), сохраняется до завершения полного выражения, содержащего вызов.
... в этот момент он умирает. Отсюда и свисание.
Литерал производит prvalue выражение, которое T&
не могу принять. T&
принимает только lvalues.
Вы можете думать об этом следующим образом: целочисленный литерал является "неживой" вещью, поскольку он нигде не имеет адреса, как вы можете связать его со ссылкой lvalue, а затем изменить его? Где этот объект будет расположен? Где будут написаны изменения?