Ответ 1
Как говорит 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 std:: swap для написания собственной реализации арифметики:: 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 их обоих. Возможно, я пропустил что-то действительно очевидное.
Btw, в этом случае я мог бы полностью специализировать каждый из arithmetic::sub
и sandwich::sub
. Абоненты будут using
того или другого, и получат правильную функцию. Однако исходный вопрос говорит о частичной специализации, поэтому можем ли мы сделать вид, что специализация - это не вариант, без меня, фактически, что HeapOfHam является шаблоном класса?