除了@Yakk 的回答之外,还有很多方法可以做到这一点。这里有 5 个。
方法一:功能特征
这更像是在更高级的模板元编程技术出现之前使用的经典技术。它仍然很方便。我们根据T
给我们想要使用的类型和常量专门化了一些结构。
template<class T>
struct FooTraits;
template<class T>
struct FooTraits<std::plus<T>>
{
using Compare = std::greater<T>;
static constexpr std::tuple<int, int> barVals{0, 10};
};
template<class T>
struct FooTraits<std::minus<T>>
{
using Compare = std::less<T>;
static constexpr std::tuple<int, int> barVals{0, -10};
};
template <class T>
void foo(const int arg1, const int arg2, int& bar)
{
using traits = FooTraits<T>;
typename traits::Compare cmp{};
cmp(arg1, arg2);
cmp(bar, std::get<0>(traits::barVals));
cmp(bar, std::get<1>(traits::barVals));
}
方法二:全专业化
另一种仍然有用的“经典”技术。您可能对这种技术很熟悉,但为了完整起见,我将其展示出来。只要你永远不需要对一个函数进行部分特化,你就可以为你需要的类型编写不同的版本:
template <class T>
void foo(const int arg1, const int arg2, int& bar);
template <>
void foo<std::plus<int>>(const int arg1, const int arg2, int& bar)
{
arg1 > arg2;
bar > 0;
bar > 10;
}
template <>
void foo<std::minus<int>>(const int arg1, const int arg2, int& bar)
{
arg1 < arg2;
bar < 0;
bar < -10;
}
方法三:标签调度
第三种将类型检查变成重载问题的经典技术。要点是我们定义了一些tag
可以实例化的轻量级结构,然后将其用作重载之间的区别。当你有一个模板化的类函数时,这通常很好用,并且你不想专门化整个类只是为了专门化所述函数。
namespace detail
{
template<class...> struct tag{};
void foo(const int arg1, const int arg2, int& bar, tag<std::plus<int>>)
{
arg1 > arg2;
bar > 0;
bar > 10;
}
void foo(const int arg1, const int arg2, int& bar, tag<std::minus<int>>)
{
arg1 < arg2;
bar < 0;
bar < -10;
}
}
template <class T>
void foo(const int arg1, const int arg2, int& bar)
{
return detail::foo(arg1, arg2, bar, detail::tag<T>{});
}
方法4:直截了当constexpr if
从 C++17 开始,我们可以使用if constexpr
块对类型进行编译时检查。这些很有用,因为如果检查失败,编译器根本不会编译该块。这通常会导致代码比以前简单得多,我们不得不使用复杂的间接方式来使用高级元编程的类或函数:
template <class T>
void foo(const int arg1, const int arg2, int& bar)
{
if constexpr (std::is_same_v<T, std::plus<int>>)
{
arg1 > arg2;
bar > 0;
bar > 10;
}
if constexpr(std::is_same_v<T, std::minus<int>>)
{
arg1 < arg2;
bar < 0;
bar < -10;
}
}
方法五:constexpr
+蹦床
蹦床是一种元编程技术,您可以使用“蹦床”函数作为调用者和您希望分派到的实际函数之间的中介。在这里,我们将使用它来映射到适当的比较类型(std::greater
或std::less
)以及我们希望比较的整数常量bar
。它比方法 4 更灵活一点。它也将关注点分开了一点。以可读性为代价:
namespace detail
{
template<class Cmp, int first, int second>
void foo(const int arg1, const int arg2, int& bar)
{
Cmp cmp{};
cmp(arg1, arg2);
cmp(bar, first);
cmp(bar, second);
}
}
template <class T>
void foo(const int arg1, const int arg2, int& bar)
{
if constexpr (std::is_same_v<T, std::plus<int>>)
return detail::foo<std::greater<int>, 0, 10>(arg1, arg2, bar);
if constexpr(std::is_same_v<T, std::minus<int>>)
return detail::foo<std::less<int>, 0, -10>(arg1, arg2, bar);
}