#c #c 20 #c -concepts
#c #c 20 #c -концепции
Вопрос:
Рассмотрим следующий пример для вычисления абсолютной разницы между двумя значениями.
template<class T>
auto abs_diff(const T _a, const T _b) {
return (_a > _b) ? (_a - _b) : (_b - _a);
}
Для некоторых встроенных типов std::abs
обеспечивает превосходную производительность, поэтому, если std::abs(T x)
она существует, я хочу ее использовать (давайте предположим, что нас не интересует различное поведение при переполнении / переполнении в случае целых чисел). С этой целью я попытался добавить следующую ограниченную шаблонную функцию
template<class T> requires requires (T x) { std::abs(x); }
auto abs_diff(const T _a, const T _b) {
return std::abs(_a - _b);
}
Теперь все типы, для которых std::abs
существует, будут использовать эту специализированную версию. Однако также все типы, которые неявно преобразуются в такой тип, будут использовать его, что нежелательно. Итак, мой вопрос: есть ли способ потребовать существования функции с точной сигнатурой в концепции (т. Е. Существования std::abs(T x)
, а не только факта std::abs(x)
компиляции).
Примечание: Приведенный выше пример в основном для иллюстрации и может быть исправлен, по крайней мере, для моего приложения, путем ограничения возвращаемого типа с помощью requires (T x) { { std::abs(x) } -> std::same_as<T>; }
. Однако меня интересует общее решение проблемы такого рода.
Комментарии:
1. «… что нежелательно». Почему это нежелательно? Есть ли что-то конкретное, чего вы пытаетесь избежать?
2. Как вы определяете «точную подпись» здесь? Должно ли существование шаблона функции, из которого может быть создан экземпляр правильной функции, также удовлетворять концепции?
3. @Barry В данном конкретном случае у меня есть класс с фиксированной точкой, который неявно преобразуется в
double
. Таким образом, хотя в точной арифметике возможно использовать абсолютную разницу между двумя такими числами с фиксированной точкой, использование вышеупомянутой функции приведет к излишне неточному результату как double. Однако существуют и другие сценарии, помимоabs_diff
примера, в котором я хотел бы предотвратить неявные приведения.4. @StoryTeller-UnslanderMonica Интересный момент, не думал об этом раньше, но я думаю, что да, так и должно быть.
5. @Haatschii: Но что, если пользователь передает a
short
?std::abs
Для этого нет явного указания. А как насчет других встроенных типов, которые не подвергаются перегрузкам и все же полагаются на не определенные пользователем преобразования для выполненияstd::abs
работы
Ответ №1:
Вместо этого мы можем проверить, может ли выражение адреса перегруженной функции amp;std::abs
преобразовываться в указатель на функцию с желаемой сигнатурой. Или мы могли бы, за исключением того, что C 20 не позволяет формировать указатели на большинство стандартных библиотечных функций.
Оставляя этот оригинальный ответ здесь для информации. Это был бы допустимый шаблон для перегруженного имени функции, которого нет в стандартной библиотеке. И это часто работает в любом случае, но мы не должны рассчитывать на то, что это продолжится с более новыми версиями библиотеки.
#include <cmath>
template<class T>
auto abs_diff(const T _a, const T _b) {
return (_a > _b) ? (_a - _b) : (_b - _a);
}
template<class T> requires requires (T (*fp)(T)) { fp = amp;std::abs; }
auto abs_diff(const T _a, const T _b) {
return std::abs(_a - _b);
}
Посмотрите тест на godbolt.
Когда этот метод используется для имени, которое включает соответствующие шаблоны функций, ограничение также может быть выполнено, если ни одна не шаблонная функция не является точным совпадением, но один наиболее специализированный шаблон функции может вывести свои аргументы так, чтобы тип специализации был точным T(T)
.
(Существует шаблон std::abs
функции , объявленный в <complex>
, но он никогда не может точно соответствовать типу T(T)
.)
Комментарии:
1. На самом деле
std::abs
может включать шаблон функции, из<complex>
. Я обновлю информацию позже.2. Выглядит великолепно, спасибо! Для
std::complex
ограниченного шаблона не рассматривается, однако это правильно, потому что в этом случае возвращаемый типstd::abs
не совпадает с типом аргумента, и подпись не совпадает.3.
std::abs
Определены ли перегрузки как адресуемые ? В целом это очень хорошее решение, но стандартные библиотечные функции внесли бы в это серьезное предостережение.4. @StoryTeller-UnslanderMonica Интересно, я не видел этого нового требования. Я просто знал правило из C 17 и ранее, сказав только, что невиртуальные функции-члены в стандартной библиотеке не обязательно имеют точные сигнатуры, описанные в Стандарте (подразумевая, я думаю, что все другие функции и шаблоны функций были «адресуемыми»).).
5. @StoryTeller-UnslanderMonica Итак, в таком случае, согласны ли вы, что вопрос несколько некорректен, потому что на самом деле неясно, что означает «если
std::abs(T x)
существует»?
Ответ №2:
В результате обсуждения ответа @aschepler я пришел к следующему решению, которое также должно работать для стандартных библиотечных функций, как std::abs
есть:
template<class T>
struct BlockImplicitConversion {
BlockImplicitConversion(const Tamp; _arg);
operator T();
};
template<class T>
auto abs_diff(const T _a, const T _b) {
return (_a > _b) ? (_a - _b) : (_b - _a);
}
template<class T> requires requires (T x) { std::abs(BlockImplicitConversion(x)); }
auto abs_diff(const T _a, const T _b) {
return std::abs(_a - _b);
}
Рациональным является то, что выполняется не более одного неявного пользовательского преобразования. Поэтому использование прокси-класса BlockImplicitConversion
предотвращает дальнейшие неявные приведения. В то же время стандартные последовательности преобразования все еще разрешены, например, для short
вызова специализированного шаблона и приведения к int. выполняется.
Комментарии:
1. Никаких потерь для меня, если вы хотите переместить галочку в свой собственный ответ, поскольку мой ответ имеет важный недостаток.