Как 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-перегрузку для первого параметра, так как «Идентичность в силе чего-либо - это Identity». Надеюсь, вы поняли эту идею.
Я думаю, что есть недостаток 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, чтобы написать свою собственную реализацию арифметической :: подразделам:
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, чтобы написать свою собственную реализацию сэндвич :: 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 их обоих. Возможно, мне не хватает чего-то действительно очевидного.
Btw, в этом случае я мог бы просто полностью специализировать каждый из и sandwich::sub
. Вызывающие вызывали бы using
один или другой, и получили бы правильную функцию. Однако исходный вопрос говорит о частичной специализации, поэтому можем ли мы сделать вид, что специализация - это не вариант, без меня на самом деле сделать HeapOfHam шаблоном класса?
Я думаю, что это зависит. В вашем случае вы называете это «fun (u)», поэтому вы не можете перегружать ('N' не появляется нигде в параметре). Но я думаю, что если «перегрузка» возможна, это предпочтительный способ. Отличным примером является 'std :: swap' или' std :: begin' или 'std :: end' (где последние два являются функциями C++ 0x). Обратите внимание, что статья Саттера была написана 9 лет назад. Не уверен, что он по-прежнему рекомендует сделать это «делегировать классу». И я думаю, что он не масштабируется красиво: не будет работать с ADL - вам придется возиться со всеми типами пространств имен и специализироваться на их шаблонах. Ничего хорошего –