Как большинство программистов на 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
затронуты, они получают желаемую частичную специализацию.
Мои вопросы: когда запись обработала бесплатные функции по шаблону (которые предназначаются, чтобы использоваться другими), необходимо ли автоматически делегировать реализацию к функции статического метода класса, так, чтобы пользователи библиотеки могли реализовать частичные специализации по желанию, или Вы просто пишете шаблонной функции нормальный путь и живете с тем, что люди не будут в состоянии специализировать их?
Как сказано в литб, 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 - моя библиотека будет выполнять все целые числа. Я мог разумно ожидать, что третья сторона определит, что будет делать умножение на октонион .
Если подумать, возведение в степень могло бы быть лучшим примером для меня, поскольку моя arithmetic :: mul
до степени смешения похожа на оператор *
, поэтому на самом деле нет в моем примере нужно специализироваться на mul
. Тогда я бы выделил / ADL-overload для первого параметра, так как «Идентичность во власти чего-либо есть идентичность». Надеюсь, вы уловили идею.
Я думаю, что у 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
. Я хочу воспользоваться преимуществами ADL в стиле std :: swap, чтобы написать свою собственную реализацию 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);
}
}
Я также хочу воспользоваться преимуществами ADL в стиле std :: swap, чтобы написать свою собственную реализацию sandwich :: 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.
}
}
Погодите минутку. Я не могу этого сделать, а? Две разные функции в разных пространствах имен с одинаковыми параметрами и разными типами возвращаемых значений: обычно это не проблема, для чего предназначены пространства имен. Но я не могу использовать их обоих. Возможно, мне не хватает чего-то действительно очевидного.
Между прочим, в этом случае я мог бы просто полностью специализировать каждый из arithmetic :: sub
и sandwich :: sub
. Вызывающие абоненты использовали бы тот или иной
и получили бы нужную функцию. Однако в исходном вопросе говорится о частичной специализации, поэтому можем ли мы притвориться, что специализация не является вариантом, без того, чтобы я фактически сделал HeapOfHam шаблоном класса?
Если вы пишете библиотеку, которая будет использоваться в других местах или другими людьми, используйте struct/class. Это больше кода, но пользователи вашей библиотеки (возможно, будущие пользователи!) будут вам благодарны. Если это код для одного пользователя, потеря частичной специализации не повредит вам.