Это звучит как основной вариант использования для отправки тегов :
Мы создаем два разных класса тегов, чтобы различать два варианта использования
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 Вторая перегрузка всегда существует, но из-за аргумента variadic в стиле C она никогда не используется, если первая перегрузка не совпадает.
Затем мы можем отправить любую версию, создав соответствующуюtag:
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
), но вы также можете легко сделать ее совместимой с C ++ 11, используя вместо этого typename std::enable_if<...>::type
и std::is_same<...>::value
- это просто более многословно.
1 Это то, что вы указали в вопросе - но это опасно!Например, если вы используете целые числа, вы будете использовать интерполяцию ближайшего соседа, потому что *
возвращает float
, а не int
.Вместо этого вы должны проверить, возвращает ли выражение (*t + *t) * 0.5f
что-то конвертируемое обратно в T
, используя такой тест, как std::is_constructible_v<T, decltype((*t + *t) * 0.5f)>
В качестве бонуса,Вот реализация c ++ 20 , основанная на концепциях, которая больше не нуждается в тегах (как кратко упомянуто в комментариях).К сожалению, на этом уровне еще нет компилятора, поддерживающего 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
}