Почему компилятор c ++ выдает ошибку при использовании функции `std :: max ()` для двух разных типов числовых переменных - PullRequest
2 голосов
/ 23 июня 2019

Есть ли причина, по которой компилятор c ++ выдает ошибку при использовании двух разных числовых типов переменных в функции std::max()? (например, int и long).

Я имею в виду что-то вроде: "Иногда у нас возникает эта проблема при использовании функции std::max() для двух разных числовых типов переменных, поэтому компилятор выдает ошибку, чтобы предотвратить эту проблему".

Ответы [ 3 ]

5 голосов
/ 23 июня 2019

Компилятор выдает ошибку, потому что он не может выполнить вывод типа для аргумента шаблона std::max. Вот как объявляется шаблон std::max: для обоих аргументов используется один и тот же тип (параметр шаблона). Если аргументы имеют разные типы, вычет становится неоднозначным.

Если вы решите проблему неопределенности дедукции, явно указав аргумент шаблона, вы сможете использовать различные типы в качестве std::max аргументов

std::max(1, 2.0); // Error
std::max<double>(1, 2.0); // OK

Причина, по которой std::max настаивает на использовании общего типа для своих аргументов (вместо использования двух независимых типов), описана в ответе @ bolov: функция на самом деле хочет вернуть ссылку на максимум значение.

3 голосов
/ 23 июня 2019

std::max возвращает ссылку на аргумент, который имеет максимальное значение. Основная причина этого заключается в том, что это универсальная функция, и поэтому ее можно использовать с типами, дорогостоящими для копирования. Также вам может понадобиться просто ссылка на объект, а не его копия.

И поскольку он возвращает ссылку на аргумент, все аргументы должны быть одного типа.

2 голосов
/ 23 июня 2019

Прямой ответ на вопрос заключается в том, что std::min и std::max принимают только один параметр шаблона, который определяет типы обоих аргументов. Если / когда вы пытаетесь передать аргументы разных типов, компилятор не может решить, какой из этих двух типов использовать для аргумента шаблона, поэтому код неоднозначен. Как изначально определено в C ++ 98, std::min и std::max имели такие подписи (C ++ 03, § [lib.alg.min.max]):

template<class T> const T& min(const T& a, const T& b);

template<class T, class Compare>
const T& min(const T& a, const T& b, Compare comp);

template<class T> const T& max(const T& a, const T& b);

template<class T, class Compare>
const T& max(const T& a, const T& b, Compare comp);

Итак, основная идея здесь в том, что функция получает два объекта по ссылке и возвращает ссылку на один из этих объектов. Если бы он получал объекты двух разных типов, он не смог бы вернуть ссылку на входной объект, потому что один из объектов обязательно был бы другого типа, чем тот, который он возвращал (поэтому @bolov правильно относится к этой части, но Я не думаю, что это действительно целая история).

С современным компилятором / стандартной библиотекой, если вы не имеете дела со значениями вместо ссылок, вы можете довольно легко написать код в таком общем порядке:

template <class T, class U>
std::common_type<T, U> min(T const &a, U const &b) { 
    return b < a ? b : a;
}

template <class T, class U>
std::common_type<T, U> max(T const &a, U const &b) { 
    return a < b ? b : a;
}

Это позволяет довольно легко справиться с вашим случаем передачи int и long (или других пар типов, если std::common_type может вывести для них какой-то общий тип, а a<b - это определено для объектов двух типов.

Но в 1998 году, даже если бы было доступно std::common_type, так что это было легко сделать, это решение, вероятно, не было бы принято (и, как мы увидим, до сих пор остается вопрос, является ли оно идея) - в то время, многие люди все еще думали с точки зрения большого количества наследования, так что было (более или менее) само собой разумеющимся, что вы часто используете его в ситуациях, когда оба аргумента действительно были некоторого производного типа, что-то на этот общий порядок:

class Base { 
// ...
    virtual bool operator<(Base const &other);
};

class Derived1 : public Base {
    // ...
};
class Derived2 : public Base {
    // ...
};

Derived1 d1;
Derived2 d2;

Base &b = std::max(d1, d2);

В этом случае вышеприведенная версия, которая возвращает значение вместо возврата ссылки, может привести к серьезной проблеме. common_type<Derived1, Derived2> будет Base, поэтому мы в конечном итоге нарежем аргумент, чтобы создать объект типа Base, и вернем его. Это редко обеспечивало бы желаемое поведение (а в некоторых случаях, например, если Base был бы абстрактным базовым классом, он даже не компилировался).

Есть еще один момент, на который, вероятно, стоит обратить внимание: даже если применить его в на первый взгляд простой ситуации, std::common_type может дать результаты, которых вы не ожидаете. Например, давайте рассмотрим вызов шаблона, определенного выше, как:

auto x = min(-1, 1u);

В связи с этим возникает очевидный вопрос: какого типа будет x?

Несмотря на то, что мы передали его int и unsigned, тип результата (по крайней мере, потенциально) ни int, ни unsigned!

...