要求存在精确函数签名的 C++20 概念

C++20 concept which requires the existence of an exact function signature

考虑以下计算两个值之间绝对差的示例。

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) 存在我想使用它(假设我们对不同的 under-/overflow 行为不感兴趣整数的情况)。为此我尝试添加如下约束模板函数

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>; } 约束 return 类型来修复.但是我对这类问题的一般解决方案很感兴趣。

我们可以测试重载函数地址表达式 &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 = &std::abs; }
auto abs_diff(const T _a, const T _b) {
    return std::abs(_a - _b);
}

看到一个test on godbolt.

当这个技术用于包含匹配函数模板的名称时,如果没有 non-template 函数是完全匹配的,也可以满足约束,但是一个 most-specialized 函数模板可以推导出它参数,以便专门化的类型正好是 T(T).

(在<complex>中声明了一个std::abs函数模板,但它永远不能完全匹配类型T(T)。)

作为对@aschepler 回答的讨论的结果,我提出了以下解决方案,它也适用于标准库函数,因为 std::abs 是一个:

template<class T>
struct BlockImplicitConversion {
    BlockImplicitConversion(const T& _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);
}

合理的是,最多执行一次隐式 user-defined 转换。因此使用代理 class BlockImplicitConversion 可以防止进一步的隐式转换。同时仍然允许使用标准转换序列,例如对于 short,调用专用模板并执行转换为 int。

Godbolt example