正如大多数C ++程序员应该知道的那样,不允许使用自由函数的部分模板特化。例如,以下是非法的C ++:
template <class T, int N>
T mul(const T& x) { return x * N; }
template <class T>
T mul<T, 0>(const T& x) { return T(0); }
// error: function template partial specialization ‘mul<T, 0>’ is not allowed
但是,允许对类/结构的部分模板特化进行,并且可以利用它来模仿自由函数的部分模板特化的功能。例如,最后一个示例中的目标目标可以通过使用:
来实现template <class T, int N>
struct mul_impl
{
static T fun(const T& x) { return x * N; }
};
template <class T>
struct mul_impl<T, 0>
{
static T fun(const T& x) { return T(0); }
};
template <class T, int N>
T mul(const T& x)
{
return mul_impl<T, N>::fun(x);
}
它更笨重,更简洁,但它完成了工作 - 就mul
的用户而言,他们获得了所需的部分专业化。
我的问题是:在编写模板化的自由函数(打算由其他人使用)时,是否应该自动将实现委托给类的静态方法函数,以便库的用户可以随意实现部分特化,或者你只是以正常方式编写模板化函数,并接受人们无法专门化它们的事实?
答案 0 :(得分:3)
正如litb所说,ADL在可以工作的地方更胜一筹,基本上只要模板参数可以从调用参数中推断出来:
#include <iostream>
namespace arithmetic {
template <class T, class S>
T mul(const T& x, const S& y) { return x * y; }
}
namespace ns {
class Identity {};
// this is how we write a special mul
template <class T>
T mul(const T& x, const Identity&) {
std::cout << "ADL works!\n";
return x;
}
// this is just for illustration, so that the default mul compiles
int operator*(int x, const Identity&) {
std::cout << "No ADL!\n";
return x;
}
}
int main() {
using arithmetic::mul;
std::cout << mul(3, ns::Identity()) << "\n";
std::cout << arithmetic::mul(5, ns::Identity());
}
输出:
ADL works!
3
No ADL!
5
重载+ ADL通过部分专门化arithmetic::mul
的函数模板S = ns::Identity
来实现您的目标。但它确实依赖于调用者以允许ADL的方式调用它,这就是为什么你从不明确地调用std::swap
。
所以问题是,您希望您的图书馆用户必须部分专门化您的功能模板?如果他们要将它们专门用于类型(通常是算法模板的情况),请使用ADL。如果他们要将它们专门用于整数模板参数,就像在你的例子中一样,那么我猜你必须委托给一个类。但我通常不希望第三方定义3乘法乘法 - 我的库将全部整数。我可以合理地期望第三方定义乘以octonion的乘法。
考虑到这一点,取幂可能是我使用的一个更好的例子,因为我的arithmetic::mul
与operator*
容易混淆,所以没有实际需要专门化mul
在我的例子中。然后我会为第一个参数专门化/ ADL重载,因为“身份对任何东西的认同都是身份”。但是,希望你能得到这个想法。
我认为ADL有一个缺点 - 它有效地平滑了命名空间。如果我想使用ADL为我的班级“实施”arithmetic::sub
和sandwich::sub
,那么我可能会遇到麻烦。我不知道专家们对此有什么看法。
我的意思是:
namespace arithmetic {
// subtraction, returns the difference of lhs and rhs
template<typename T>
const T sub(const T&lhs, const T&rhs) { return lhs - rhs; }
}
namespace sandwich {
// sandwich factory, returns a baguette containing lhs and rhs
template<typename SandwichFilling>
const Baguette sub(const SandwichFilling&lhs, const SandwichFilling&rhs) {
// does something or other
}
}
现在,我有一个ns::HeapOfHam
类型。我想利用std :: swap-style ADL来编写我自己的arithmetic :: sub:
namespace ns {
HeapOfHam sub(const HeapOfHam &lhs, const HeapOfHam &rhs) {
assert(lhs.size >= rhs.size && "No such thing as negative ham!");
return HeapOfHam(lhs.size - rhs.size);
}
}
我也想利用std :: swap-style ADL来编写我自己的三明治实现:sub:
namespace ns {
const sandwich::Baguette sub(const HeapOfHam &lhs, const HeapOfHam &rhs) {
// create a baguette, and put *two* heaps of ham in it, more efficiently
// than the default implementation could because of some special
// property of heaps of ham.
}
}
等一下。我不能那样做,可以吗?具有相同参数和不同返回类型的不同命名空间中的两个不同函数:通常不是问题,这就是命名空间的用途。但我不能ADL-ify他们俩。可能我错过了一些非常明显的东西。
顺便说一句,在这种情况下,我可以完全专注于arithmetic::sub
和sandwich::sub
。呼叫者将using
一个或另一个,并获得正确的功能。最初的问题谈到了部分专业化,所以我们可以假装专业化不是一种选择,没有我实际上让HeapOfHam成为一个类模板吗?
答案 1 :(得分:1)
如果您正在编写要在别处或其他人使用的库,请执行struct / class。这是更多的代码,但你的图书馆的用户(可能是你的未来!)会感谢你。如果这是一个使用代码,部分专业化的损失不会伤害你。