2011-06-03 6 views
37

¿Cuál es el método preferido para usar std :: rel_ops para agregar el conjunto completo de operadores relacionales a una clase?Uso idiomático de std :: rel_ops

This documentación sugiere una using namespace std::rel_ops, pero esto parece ser profundamente defectuoso, ya que significaría que la inclusión de la cabecera de la clase implementado de esta manera también añadiría operadores relacionales completo a todas las demás clases con un operador definido < y operador ==, incluso si eso no fue deseado. Esto tiene el potencial de cambiar el significado del código de maneras sorprendentes.

Como una nota al margen: he estado usando Boost.Operators para hacer esto, pero todavía tengo curiosidad acerca de la biblioteca estándar.

+10

Otro problema con 'using namespace std :: rel_ops' es que los operadores no se consideran para la búsqueda dependiente de argumentos. Esto significa que, por ejemplo, 'std :: greater ' no se compilará (mientras que si se definiera un 'operador' adecuado en el mismo espacio de nombres como 'my_type', o en el espacio de nombres global). –

+0

@MikeSeymour He agregado una solución (no portátil, pero bastante portátil en la práctica) que hace que ADL funcione con rel_ops. – bames53

Respuesta

20

Creo que la técnica preferida es no utilizar std::rel_ops en todos. La técnica utilizada en boost::operator (link) parece ser la solución habitual .

Ejemplo:

sobrecargas de operadores
#include "boost/operators.hpp" 

class SomeClass : private boost::equivalent<SomeClass>, boost::totally_ordered<SomeClass> 
{ 
public: 
    bool operator<(const SomeClass &rhs) const 
    { 
     return someNumber < rhs.someNumber; 
    } 
private: 
    int someNumber; 
}; 

int main() 
{ 
    SomeClass a, b; 
    a < b; 
    a > b; 
    a <= b; 
    a >= b; 
    a == b; 
    a != b; 
} 
+11

Esto realmente no responde la pregunta. Muchos proyectos de desarrollo C++ no incluyen impulso debido a su tamaño y complejidad. –

+7

La respuesta podría extenderse a, ya sabes, _describe_ la técnica utilizada en 'boost :: operator'. En términos generales, la respuesta es correcta en el sentido de que la forma idiomática (o cualquier otra cosa) para usar 'std :: rel_ops' es simplemente no hacerlo. Incluso rodar su propia plantilla de clase con los operadores es mejor. –

27

el camino para las clases definidas por el usuario estaba destinado a trabajo es a través de las operaciones de búsqueda depende argumento. ADL permite que los programas y bibliotecas eviten saturar el espacio de nombres global con sobrecargas de operadores, pero aún permite el uso conveniente de los operadores; Es decir, sin calificación explícita del espacio de nombres, que no es posible hacer con la sintaxis del operador infijo a + b y requeriría la sintaxis de la función normal your_namespace::operator+ (a, b).

ADL, sin embargo, no solo busca en todas partes cualquier posible sobrecarga del operador. ADL está restringido a mirar solo las clases 'asociadas' y los espacios de nombres. El problema con std::rel_ops es que, como se especifica, este espacio de nombres nunca puede ser un espacio de nombres asociado de ninguna clase definida fuera de la biblioteca estándar, y por lo tanto, ADL no puede funcionar con dichos tipos definidos por el usuario.

Sin embargo, si está dispuesto a hacer trampa, puede hacer que std::rel_ops funcione.

Los espacios de nombres asociados se definen en C++ 11 3.4.2 [basic.lookup.argdep]/2. Para nuestros propósitos, el hecho importante es que el espacio de nombres del que una clase base es miembro es un espacio de nombres asociado de la clase heredada, y por lo tanto, ADL comprobará esos espacios de nombres para las funciones apropiadas.

lo tanto, si el siguiente:

#include <utility> // rel_ops 
namespace std { namespace rel_ops { struct make_rel_ops_work {}; } } 

fueron (de alguna manera) a encontrar su camino en una unidad de traducción, a continuación, en implementaciones compatibles (véase la siguiente sección) usted podría entonces definir sus propios tipos de clases de este modo:

namespace N { 
    // inherit from make_rel_ops_work so that std::rel_ops is an associated namespace for ADL 
    struct S : private std::rel_ops::make_rel_ops_work {}; 

    bool operator== (S const &lhs, S const &rhs) { return true; } 
    bool operator< (S const &lhs, S const &rhs) { return false; } 
} 

y luego ADL funcionaría para su tipo de clase y encontraría los operadores en std::rel_ops.

#include "S.h" 

#include <functional> // greater 

int main() 
{ 
    N::S a, b; 

    a >= b;      // okay 
    std::greater<N::s>()(a, b); // okay 
} 

Por supuesto añadiendo make_rel_ops_work mismo técnico hace que el programa tenga un comportamiento indefinido debido a que C++ no permite que los programas de usuario para agregar declaraciones a std.Como ejemplo de cómo eso realmente importa y por qué, si hace esto, puede tomarse la molestia de verificar que su implementación realmente funcione correctamente con esta adición, considere:

Más arriba muestro una declaración de make_rel_ops_work que sigue #include <utility>. Uno podría ingenuamente esperar que incluir esto aquí no importa y que mientras el encabezado esté incluido en algún momento antes del uso de las sobrecargas del operador, entonces ADL funcionará. La especificación, por supuesto, no ofrece esa garantía y existen implementaciones reales donde ese no es el caso.

sonido metálico con libC++, debido a libc 'uso s de los espacios de nombres en línea, será (IIUC) consideran que la declaración de make_rel_ops_work estar en un espacio de nombres distinto del espacio de nombres que contiene los <utility> sobrecargas de operadores menos <utility>' ++ declaración de std::rel_ops s es lo primero. Esto se debe a que, técnicamente, std::__1::rel_ops y std::rel_ops son espacios de nombres distintos incluso si std::__1 es un espacio de nombres en línea. Pero si clang ve que la declaración del espacio de nombres original para rel_ops se encuentra en un espacio de nombre en línea __1, tratará una declaración namespace std { namespace rel_ops { como extensión std::__1::rel_ops en lugar de como un nuevo espacio de nombres.

Creo que este comportamiento de extensión de espacio de nombres es una extensión de clang en lugar de ser especificado por C++, por lo que es posible que ni siquiera pueda confiar en esto en otras implementaciones. En particular, gcc no se comporta de esta manera, pero afortunadamente libstdC++ no usa espacios de nombres en línea. Si no quiere depender de esta extensión luego de sonido metálico/libC++ puede escribir:

#include <__config> 
_LIBCPP_BEGIN_NAMESPACE_STD 
namespace rel_ops { struct make_rel_ops_work {}; } 
_LIBCPP_END_NAMESPACE_STD 

pero, obviamente, entonces usted necesita para implementaciones de otras bibliotecas que utiliza. Mi declaración más simple de make_rel_ops_work funciona para clang3.2/libC++, gcc4.7.3/libstdC++ y VS2012.

+0

O simplemente podría escribir su propia versión de 'std :: rel_ops' (posiblemente implementado recurriendo a' std :: rel_ops') y evitar UB – Justin

1

No es el mejor, pero puede usar using namespace std::rel_ops como un detalle de implementación para implementar los operadores de comparación en su tipo. Por ejemplo:

template <typename T> 
struct MyType 
{ 
    T value; 

    friend bool operator<(MyType const& lhs, MyType const& rhs) 
    { 
     // The type must define `operator<`; std::rel_ops doesn't do that 
     return lhs.value < rhs.value; 
    } 

    friend bool operator<=(MyType const& lhs, MyType const& rhs) 
    { 
     using namespace std::rel_ops; 
     return lhs.value <= rhs.value; 
    } 

    // ... all the other comparison operators 
}; 

Mediante el uso de using namespace std::rel_ops;, permitimos ADL a las operaciones de búsqueda operator<= si está definida para el tipo, pero cae de nuevo en el definido en std::rel_ops lo contrario.

Esto todavía es un dolor, ya que todavía tiene que escribir una función para cada uno de los operadores de comparación.