2010-03-08 12 views
11

Como la mayoría de los programadores de C++ deben saber, no se permite la especialización parcial de plantillas de funciones gratuitas. Por ejemplo, el siguiente es C ilegal ++:Especialización parcial de plantillas de funciones gratuitas: prácticas recomendadas

template <class T, int N> 
T mul(const T& x) { return x * N; } 

template <class T> 
T mul<T, 0>(const T& x) { return T(0); } 

// error: function template partial specialization ‘mul<T, 0>’ is not allowed 

Sin embargo, especialización de plantilla parcial de clases/estructuras se animales, y puede ser explotado para imitar la funcionalidad de especialización de plantilla parcial de las funciones libres. Por ejemplo, el objetivo de destino en el último ejemplo se puede lograr mediante el uso de:

template <class T, int N> 
struct mul_impl 
{ 
    static T fun(const T& x) { return x * N; } 
}; 

template <class T> 
struct mul_impl<T, 0> 
{ 
    static T fun(const T& x) { return T(0); } 
}; 

template <class T, int N> 
T mul(const T& x) 
{ 
    return mul_impl<T, N>::fun(x); 
} 

Es más voluminosos y menos concisa, pero hace el trabajo - y en lo que se refiere a los usuarios de mul, consiguen la especialización parcial deseada.


Mi pregunta es: al escribir funciones gratuitas con plantilla (que están destinados a ser utilizados por otros), en caso de que delegar de forma automática la ejecución de una función método estático de una clase, por lo que los usuarios de la biblioteca pueden aplicar especializaciones parciales a voluntad, ¿o simplemente escribe la función de plantilla de la manera habitual, y vive con el hecho de que las personas no podrán especializarlas?

+2

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 –

Respuesta

3

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?

+0

No estoy seguro acerca del problema 'sub' para espacios de nombres aplastados. Creo que uno tendría el mismo problema si 'sub' fuera una función miembro. ¿Cuál sería el operando de esos dos subs? Si sandwich: no puedo imaginar cómo podríamos aritméticamente un sándwich, por lo que un 'sándwich' definido en' aritmética' parece improbable. Si MyInt: como las conversiones implícitas efectivamente no se consideran (si 'sandwich' tiene un' (MyInt) '(para el número de planchas de queso?) Ctor y llamamos' sub (a, b) ', no vamos a encontrar' sub' en sandwich, por supuesto), al principio no me veo como un problema. Me alegraría que elaborases esto. –

+0

@litb: No te sigo del todo, así que voy a editar y veremos si estamos hablando de lo mismo. –

+0

@lit: hecho. Si no sabe la respuesta, tal vez haga una pregunta. –

1

Si está escribiendo una biblioteca para ser utilizada en otro lugar o por otras personas, haga lo de struct/class. Es más código, pero los usuarios de su biblioteca (¡posiblemente un futuro!) Se lo agradecerán. SI se trata de un código de uso, la pérdida de la especialización parcial no le hará daño.

Cuestiones relacionadas