2011-10-19 12 views
9

std::forward_list proporciona insert_after y erase_after miembros que pueden no necesitar acceder realmente al objeto std::forward_list. Por lo tanto, se pueden implementar como funciones de miembro static y llamar sin un objeto de lista, lo cual es útil para un objeto que quiere eliminarse de una lista, que es un uso muy común. EDITAR: Esta optimización solo se aplica a las especializaciones forward_list en std::allocator o asignadores sin estado definidos por el usuario.¿Pueden los miembros de std :: forward_list implementarse como estáticos?

¿Puede una implementación conforme al estándar hacer esto?

§17.6.5.5/3 dice

A call to a member function signature described in the C++ standard library behaves as if the implementation declares no additional member function signatures.

con una nota al pie

A valid C++ program always calls the expected library member function, or one with equivalent behavior. An implementation may also define additional member functions that would otherwise not be called by a valid C++ program.

No me queda claro si la adición de static crearía una función miembro "diferente", pero la eliminación de un (implícita El argumento no debería romper nada que añadir argumentos no cumplidos no lo haría, y eso es legal. (No se puede legalmente tomar un PTMF a ninguna función de miembro estándar.)

Me parece que a la biblioteca se le debe permitir hacer esto, pero no estoy seguro de si se romperá alguna regla. ¿Y cuán normativos son los prototipos de función miembro enumerados?

+3

Las operaciones de lista de mutaciones requieren acceso al asignador de la lista, por lo que dudo que puedan ser estáticas (especialmente con las nuevas asignaciones con estado). –

+0

Aún así, la plantilla puede ser especializada para el caso extremadamente común de 'std :: allocator' y del mismo modo para el usuario si lo desea. – Potatoswatter

+0

¿Cómo se especializaría esto basándose en el conocimiento sobre * iterator * solo? El iterador no sabe a qué lista pertenece, ni qué asignador utiliza esa lista. –

Respuesta

9

La norma dice que puede salirse con la suya si nadie puede notar la diferencia. Y tiene razón en que uno no puede crear legalmente un PTMF en forward_list, por lo que está seguro de esa manera.

Ya se ha señalado el peligro de las asignaciones personalizadas. Pero incluso para std::allocator<T> existe el peligro de que alguien se especialice en std::allocator<MyType> y luego detecte que no se ha llamado al allocator::construct/destroy.

Bien, pero ¿puede uno especial decir std::forward_list<int> (sin asignador personalizado, sin valor definido por el usuario) y hacer insert_after estático?

No. Este cambio sería detectable con las nuevas capacidades de SFINAE. He aquí una demostración:

#include <memory> 
#include <iostream> 

template <class T, class A = std::allocator<T>> 
class forward_list 
{ 
public: 
    typedef T value_type; 
    struct const_iterator {}; 
    struct iterator {}; 

    iterator insert_after(const_iterator p, const T& x); 
}; 

template <class C> 
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x) 
    -> decltype(C::insert_after(p, x)) 
{ 
    std::cout << "static\n"; 
    return typename C::iterator(); 
} 

template <class C> 
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x) 
    -> decltype(c.insert_after(p, x)) 
{ 
    std::cout << "not static\n"; 
    return typename C::iterator(); 
} 

int main() 
{ 
    ::forward_list<int> c; 
    test(c, ::forward_list<int>::const_iterator(), 0); 
} 

Este programa se ejecuta e imprime:

not static 

Pero si hago insert_after estática:

static iterator insert_after(const_iterator p, const T& x); 

Entonces me sale un error de tiempo de compilación:

test.cpp:34:5: error: call to 'test' is ambiguous 
    test(c, ::forward_list<int>::const_iterator(), 0); 
    ^~~~ 
test.cpp:16:6: note: candidate function [with C = forward_list<int, std::__1::allocator<int> >] 
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x) 
    ^
test.cpp:24:6: note: candidate function [with C = forward_list<int, std::__1::allocator<int> >] 
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x) 
    ^
1 error generated. 

Diferencia detectada.

Por lo tanto, no es conforme hacer forward_list::insert_after estática.

actualización

Si desea realizar la sobrecarga "estática" exigible, sólo hay que hacerlo un poco más deseable que la sobrecarga "no es estático".Una forma de hacer que está cambiando la sobrecarga "no es estático" a:

template <class C, class ...Args> 
auto test(C& c, typename C::const_iterator p, const typename C::value_type& x, Args...) 
    -> decltype(c.insert_after(p, x)) 
{ 
    std::cout << "not static\n"; 
    return typename C::iterator(); 
} 

Ahora la prueba se imprimirá o bien "estática" o "no es estático", dependiendo de si la función insert_after miembro es estático o no.

+0

No había pensado en 'std :: allocator' especializado por el usuario, gran punto. Pero, ¿no es la sobrecarga "estática" una plantilla sin una especialización bien formada, por lo tanto, mal formada sin necesidad de diagnóstico? – Potatoswatter

+0

Respuesta actualizada con "detector estático" totalmente funcional. –

+0

Bien ... no es difícil, pero ¿cómo es esto diferente de, por ejemplo, detectar un argumento predeterminado al encontrar el tipo de una función miembro no sobrecargada? Ahora la plantilla produce una especialización válida, pero todavía parece ser UB porque la expresión 'C :: insert_after()' no está definida por el estándar. Del mismo modo, ¿hay una implementación de varias plantillas de 'Allocator :: construct' no conformes en C++ 03 porque se puede llamar como' constructo <> (...) '? Esta es realmente la esencia de la pregunta original, en cuanto a la profundidad de la especificación de la biblioteca. – Potatoswatter

Cuestiones relacionadas