Como litb dice, ADL es superior donde se puede trabajar, que es básicamente siempre que los parámetros de plantilla se pueden deducir de los parámetros de llamada:
#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());
}
Salida:
ADL works!
3
No ADL!
5
sobrecarga + ADL logra lo que usted hubiera logrado al especializar parcialmente la plantilla de función arithmetic::mul
para S = ns::Identity
. Pero depende de que la persona que llama lo llame de una manera que permita ADL, por lo que nunca llama al std::swap
explícitamente.
Entonces la pregunta es, ¿qué esperas que los usuarios de tu biblioteca tengan para especializar parcialmente tus plantillas de funciones? Si van a especializarlos para los tipos (como es normalmente el caso con las plantillas de algoritmo), use ADL. Si van a especializarlos para los parámetros de la plantilla entera, como en su ejemplo, entonces supongo que debe delegar en una clase. Pero normalmente no espero que un tercero defina qué debe hacer la multiplicación por 3: mi biblioteca hará todos los enteros. Podría razonablemente esperar que un tercero defina qué multiplicación por un octonion hará.
Ahora que lo pienso de ella, exponenciación podría haber sido un mejor ejemplo para que yo use, ya que mi arithmetic::mul
es confusamente similar a operator*
, así que no hay necesidad real de especializarse mul
en mi ejemplo. Luego, me especializaría/ADL-sobrecargaría el primer parámetro, ya que "la identidad del poder de cualquier cosa es identidad". Espero que entiendas la idea, sin embargo.
Creo que hay una desventaja en ADL: alisa de manera efectiva los espacios de nombres. Si quiero usar ADL para "implementar" arithmetic::sub
y sandwich::sub
para mi clase, entonces podría tener problemas. No sé lo que los expertos tienen que decir al respecto.
lo cual quiero decir:
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
}
}
Ahora, tengo un tipo ns::HeapOfHam
. Quiero aprovechar la ADL std :: estilo de intercambio de escribir mi propia aplicación de la aritmética :: substitución:
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);
}
}
También quiero aprovechar la ADL std :: estilo de intercambio de escribir mi propia aplicación de sándwich :: 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.
}
}
Espere un minuto. No puedo hacer eso, ¿verdad? Dos funciones diferentes en diferentes espacios de nombres con los mismos parámetros y diferentes tipos de devolución: generalmente no es un problema, para eso están los espacios de nombres. Pero no puedo ADL-ify a los dos. Posiblemente me esté perdiendo algo realmente obvio.
Por cierto, en este caso podría especializarme completamente cada uno de arithmetic::sub
y sandwich::sub
. Los llamadores marcarían using
uno u otro, y obtendrían la función correcta. La pregunta original habla de especialización parcial, entonces, ¿podemos pretender que la especialización no es una opción, sin que yo realmente haga de HeapOfHam una plantilla de clase?
Creo que depende. En tu caso, lo llamas 'divertido (u)', por lo que no puedes sobrecargar ('N' no aparece en el parámetro). Pero creo que si la "sobrecarga" es posible, es la forma preferida. El ejemplo excelente es 'std :: swap' o' std :: begin' o 'std :: end' (donde los dos últimos son funciones de C++ 0x). Observe que el artículo de Sutter fue escrito hace 9 años. No estoy seguro de si todavía recomienda hacerlo de la manera "delegado a la clase". Y creo que no escala muy bien: no funcionará con ADL: tendrás que manipular todo tipo de espacios de nombres y especializar sus plantillas. No es bueno –