Шаблон принимает 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, а затем изменить его? Где этот объект будет расположен? Где будут написаны изменения?

Другие вопросы по тегам