Как говорит 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 - моя библиотека будет делать все целые числа. Я мог бы разумно ожидать, что третья сторона определит, что будет делать умножение на октонион .
Если подумать, возведение в степень, возможно, было бы лучшим примером для меня, поскольку мой 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
. Я хочу использовать 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.
}
}
Подождите минутку. Я не могу этого сделать, не так ли? Две разные функции в разных пространствах имен с одинаковыми параметрами и разными типами возвращаемых данных: обычно это не проблема, для этого и нужны пространства имен. Но я не могу ADL-ify их обоих. Возможно, я упускаю что-то действительно очевидное.
Кстати, в этом случае я мог бы просто полностью специализировать каждый из arithmetic::sub
и sandwich::sub
. Вызывающие абоненты будут using
один или другой, и получат правильную функцию. Оригинальный вопрос говорит о частичной специализации, однако, можем ли мы притвориться, что специализация не вариант, без моего фактического превращения HeapOfHam в шаблон класса?