要求存在精确函数签名的 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。
考虑以下计算两个值之间绝对差的示例。
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。