Как сказано в 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-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
. Вызывающие абоненты будут using
то или другое и получат нужную функцию. Однако в исходном вопросе говорится о частичной специализации, поэтому можем ли мы притвориться, что специализация - это не вариант, без того, чтобы я фактически сделал HeapOfHam шаблоном класса?
person
Community
schedule
08.03.2010
fun<U, N>(u)
, поэтому вы не можете перегрузить (N
нигде не отображается в параметре). Но я думаю, что если возможна перегрузка, это предпочтительный способ. Отличный пример -std::swap
,std::begin
илиstd::end
(где последние два являются функциями C ++ 0x). Обратите внимание, что статья Саттера была написана 9 лет назад. Не уверен, что он по-прежнему рекомендует делать это как делегат класса. И я думаю, что это плохо масштабируется: не будет работать с ADL - вам придется возиться со всевозможными пространствами имен и специализировать их шаблоны. Нет ничего хорошего - person Johannes Schaub - litb   schedule 08.03.2010