这听起来像是 标签调度的主要用例:
我们创建了两个不同的标签类来区分这两个用例
struct linear_tag {};
struct nn_tag {};
template <typename T>
T impl(T a, T b, float c, linear_tag) {
// linear interpolation here
}
template <typename T>
T impl(T a, T b, float c, nn_tag) {
// nearest neighbor interpolation here
}
现在,我们需要从T中找出标签类型:
template <typename T>
linear_tag tag_for(
T* p,
std::enable_if_t<std::is_same_v<T, decltype((*p + *p) * 0.5)>>* = nullptr
);
nn_tag tag_for(...); // Fallback
第一个重载仅在对于任何T t,表达式(t + t) * 0.5f 返回另一个T 时才存在。1 第二个重载始终存在,但由于 C 风格的可变参数, 除非第一个重载不匹配,否则永远不会使用它。
然后,我们可以通过创建适当的标签来分派到任一版本:
template <typename T>
T interpolate(T a, T b, float c) {
return impl(a, b, c, decltype(tag_for(static_cast<T*>(nullptr))){});
}
这里,decltype(tag_for(static_cast<T*>(nullptr))) 为我们提供了正确的标签类型(作为 tag_for 正确重载的返回类型)。
您可以以很少的开销添加其他标记类型,并在enable_if_t 中测试任意复杂的条件。这个特定版本仅是 C++17(因为 is_same_v),但您可以通过使用 typename std::enable_if<...>::type 和 std::is_same<...>::value 轻松使其与 C++11 兼容 - 它只是有点冗长。
1 这是您在问题中指定的内容 - 但它很危险!例如,如果您使用整数,您将使用最近邻插值,因为* 返回float,而不是int。您应该使用诸如std::is_constructible_v<T, decltype((*t + *t) * 0.5f)> 之类的测试来测试表达式(*t + *t) * 0.5f 是否返回可可转换 回T 的内容。
作为奖励,这是一个基于 c++20 概念的实现,不再需要标签(如 cmets 中简要提到的)。不幸的是,目前还没有编译器支持这个级别的requires,当然标准草案总是会发生变化:
template <typename T>
concept LinearInterpolatable = requires(T a, T b, float c) {
{ a + b } -> T;
{ a * c } -> T;
};
template <LinearInterpolatable T>
T interpolate(T a, T b, float c)
{
// Linear interpolation
}
template <typename T>
T interpolate(T a, T b, float c)
{
// Nearest-neighbor interpolation
}