2010-10-16 14 views
5

Dentro de un C++ función foo plantilla(), una llamada a :: bar (TT *) da el siguiente error bajo gcc 4.4.3:En la función de plantilla de C++, ¿por qué la llamada a función dependiente da el error "no declarado"?

g++ -o hello.o -c -g hello.cpp 
hello.cpp: In function 'void foo(std::vector<TT*, std::allocator<TT*> >&)': 
hello.cpp:8: error: '::bar' has not been declared 

Aquí está el código erróneo:

// hello.cpp 

#include <vector> 

template<typename TT> void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 

class Blah 
{ 
}; 

void bar(Blah *&) 
{ 
} 

int main(int argc, char *argv[]) 
{ 
    std::vector<Blah*> vec; 
    foo(vec); 

    return 0; 
} 

C++ distingue entre símbolos que dependen del parámetro de plantilla (TT, aquí) y aquellos símbolos que son independientes y pueden evaluarse inmediatamente.

Claramente, el compilador cree que mi llamada :: bar (TT *) es independiente y trata de resolverlo inmediatamente. Igualmente, la llamada a función es, depende de TT porque la llamada de función toma un parámetro de tipo TT *, por lo que el compilador debe esperar hasta la instanciación foo (vec) para resolver :: barra (TT *).

¿Es esto un error de gcc o me falta algo sutil sobre las plantillas de C++?

EDITAR: aquí hay un ejemplo un poco más complicado con dos versiones de :: bar() para aclarar que el orden de la declaración no es el problema con mi problema. Al analizar la plantilla, el compilador tiene no forma de saber si main() abajo creará la función de plantilla con TT = Blah o con TT = Argh. Por lo tanto, el compilador no debe dar un error hasta línea 35 línea 28 como muy pronto (si es que lo hace). Pero el error se da para línea 8 línea 16.

EDIT # 2: mejoró este ejemplo.

EDIT # 3: se han agregado correcciones a este ejemplo para que funcione como se desee. La barra (tt) ahora se refiere correctamente a la barra (Blah *). Justificación dada a continuación. (Gracias a todos).

// hello.cpp 
#include <vector> 

class XX {}; 
void bar(XX*) {} 

class CC { 
public: 
    void bar(); 
    void bar(int *); 
    void bar(float *); 

    template<typename TT> static void foo(std::vector<TT*> &vec); 
}; 

template<typename TT> 
void CC::foo(std::vector<TT*> &vec) { 
    using ::bar; 
    TT *tt; 
    bar(tt); 
    vec.push_back(tt); 
} 

class Argh {}; 
void bar(Argh *&aa) { aa = new Argh; } 

class Blah {}; 
void bar(Blah *&bb) { bb = new Blah; } 

int main(int argc, char *argv[]) { 
    std::vector<Blah*> vec; 
    CC::foo(vec); 
    return 0; 
} 
+0

el error dice que ":: bar" no se ha declarado algo así como "no hay prototipo correspondiente" ... ¿tal vez la barra realmente no está declarada? esto es una suposición –

+0

"Con la misma claridad, esa llamada a función depende de TT porque la llamada a función toma un parámetro de tipo TT *, por lo que el compilador debe esperar hasta la instanciación foo (vec) para resolver :: barra (TT *) . ". Incorrecto. Los meros nombres ":: barra" y "barra" no son dependientes. Existe una regla especial para hacer dependientes los nombres que de otro modo serían dependientes cuando se usan como unction, que es el texto @Chris quotes, y que solo se aplica a nombres no calificados. Lo que hay que hacer en la instanciación de todos modos es la resolución de sobrecarga en la declaración encontrada [s] con los argumentos dados (dependientes). –

+0

Ya he mencionado esto a continuación, pero esto parece estar en desacuerdo con usted: Stroustrup TC++ PL Sp Ed, Sección C.13.8.1, Nombres Dependientes: "Básicamente, el nombre de una función llamada es dependiente si es obviamente dependiente de mirando sus argumentos o sus parámetros formales ". Según ese criterio, ':: bar' es" obviamente dependiente ". –

Respuesta

5

Nobody has yet pointed out any part of the current Standard that says I can't.

C++ 03 no hace que el nombre ::bar dependiente. La dependencia se da para los nombres de tipo por tipos dependientes y para los nombres que no son de tipo por las expresiones dependientes que dependen del tipo o del valor. Si se busca un nombre en un tipo dependiente, se convierte en una expresión de identificación dependiente del tipo (14.6.2.2/3 última viñeta), y su búsqueda se retrasa hasta la creación de instancias. El nombre ::bar no es una expresión dependiente. Si se va a llamar bar(tt), una regla especial de C++ 03 en 14.2.6 dice

In an expression of the form:

postfix-expression (expression-listopt) 

where the postfix-expression is an identifier, the identifier denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2).

Así que hay que quitar el :: con el fin de que sea un identificador y para que sea dependiente de esta regla especial .

The reason I can't remove the :: is that in my real code, the template function foo is a member function of class CC, and there exist a family of overloaded member functions CC::bar(...), meaning I need to qualify ::bar(TT*) to avoid defaulting to CC::bar(...). That's what :: exists for, I'm surprised if the Standard says I can't use :: here

La forma correcta de resolverlo es introducir una declaración using en el ámbito local de su función.

namespace dummies { void f(); } 
template<typename T> 
struct S { 
    void f(); 
    void g() { 
    using dummies::f; // without it, it won't work 
    f(T()); // with ::f, it won't work 
    } 
}; 

struct A { }; 
void f(A) { } // <- will find this 

int main() { 
    S<A> a; 
    a.g(); 
} 

ADL no hará nada si la búsqueda ordinaria encuentra una función miembro de la clase. Por lo tanto, introduce una declaración de uso, por lo que la búsqueda ordinaria no encuentra una función de miembro de clase, y ADL puede avanzar las declaraciones visibles al crear instancias.

But this seems to disagree with you: Stroustrup TC++PL Sp Ed, Section C.13.8.1, Dependent Names: "Basically, the name of a function called is dependent if it is obviously dependent by looking at its arguments or at its formal parameters"

El libro de Stroustrup también está escrito para personas que posiblemente aún no conozcan C++. No tratará de cubrir todas las reglas con 100% de precisión, como es normal en estos libros. Los detalles sangrientos quedan para los lectores ISO Standard.

Además, los parámetros formales de una función no tienen nada que ver con si una llamada a función es dependiente o no. En el IS, solo los argumentos reales definen la dependencia de un nombre de función. Esto fue diferente en an old draft from 1996, que tenía la noción de implícita y dependencia explícita.Implícitamente dependencia se definió como

A name implicitly depends on a template-argument if it is a function name used in a function call and the function call would have a dif- ferent resolution or no resolution if a type, template, or enumerator mentioned in the template-argument were missing from the program.

[...]

[Example: some calls that depend on a template-argument type T are:

  1. The function called has a parameter that depends on T according to the type deduction rules (temp.deduct). For example, f(T), f(Array), and f(const T*).

  2. The type of the actual argument depends on T. For example, f(T(1)), f(t), f(g(t)), and f(&t) assuming that t has the type T.

También se da un ejemplo práctico

This ill-formed template instantiation uses a function that does not depend on a template-argument:

template<class T> class Z { 
public: 
     void f() const 
     { 
       g(1); // g() not found in Z's context. 
         // Look again at point of instantiation 
     } 
}; 

void g(int); 
void h(const Z<Horse>& x) 
{ 
     x.f(); // error: g(int) called by g(1) does not depend 
       // on template-argument ``Horse'' 
} 

The call x.f() gives rise to the specialization:

void Z<Horse>::f() { g(1); } 

The call g(1) would call g(int), but since that call does not depend on the template-argument Horse and because g(int) was not in scope at the point of the definition of the template, the call x.f() is ill- formed.

On the other hand:

void h(const Z<int>& y) 
{ 
     y.f(); // fine: g(int) called by g(1) depends 
       // on template-argument ``int'' 
} 

Here, the call y.f() gives rise to the specialization:

void Z<int>::f() { g(1); } 

The call g(1) calls g(int), and since that call depends on the tem- plate-argument int, the call y.f() is acceptable even though g(int) wasn't in scope at the point of the template definition. ]

Estas cosas se dejan a la historia, y hasta los últimos vestigios de que están desapareciendo poco a poco, aunque no impulsado de forma activa (n3126 de instancia se deshace de "explícitamente depende" en [temp.names]/p4 como un efecto secundario de otro cambio, porque la distinción entre "depende explícitamente" y "depende implícitamente" nunca ha existido en el SI).

+0

Con respecto a '@Chris le mostró la parte del estándar que': @Chris me mostró una frase del estándar C++ 0x aún no competido que @Alf señaló rápidamente era diferente del estándar actual, real, donde mi código es correcto (según C++ 98 §14.6.2). Además, mi código es intuitivamente correcto. La calificación no debe afectar la dependencia o independencia del símbolo de la plantilla por ningún motivo. Hasta ahora, en el momento de la creación de instancias, el compilador tiene mucha información para resolver correctamente :: bar (TT *). –

+0

En cuanto a ADL, ¿está diciendo que debería agregar 'using :: bar' en la parte superior de la función de plantilla? porque lo intenté y no funciona en gcc. –

+0

@Shawn es necesario declarar una función ficticia de "barra" antes de su plantilla, que luego lleva al alcance local mediante dicha declaración de uso. : Muéstranos cómo concluyes que tu código es válido de acuerdo con ese párrafo (por modificando tu pregunta).No sirve de nada que nos digas que tu código es válido de acuerdo con eso. Le he mostrado que 14.6.2 solo se aplica a los identificadores, y ":: bar" no es un identificador. También le mostré que ADL no hará nada por nombres calificados. Si rechazas lo que digo, no puedo ayudarte. –

1

Esto debería funcionar, el problema es el orden de la declaración:

// hello.cpp 

#include <vector> 


class Blah 
{ 
public: 

}; 

void bar(Blah *&) 
{ } 

template<typename TT> void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 


int main(int argc, char *argv[]) 
{ 
    std::vector<Blah*> vec; 
    foo(vec); 

    return 0; 
} 
+0

También vale la pena señalar que, dado que no proporcionó ningún argumento que no sea Blah * & in bar(), solo los tipos que son convertibles a Blah * e implícitamente son aceptables como TT *, que es prácticamente Blah * . – Puppy

+0

No, esta respuesta es incorrecta. Podría haber muchas versiones de barra() teniendo diferentes tipos de punteros para su único parámetro. Algunos de ellos ni siquiera han sido inventados todavía. Ese es el punto de tener una plantilla involucrada. –

+0

Pero no hay muchas versiones de barra(). Solo hay uno. ¿Por qué una función con plantilla llama a una función sin plantilla con un argumento dependiente del tipo de plantilla? Simplemente está limitando el rango de argumentos de plantilla a los aceptados por la función sin plantilla, desafiando su propósito. – Puppy

0

Su código funciona bien en VS2005. por lo que parece un error en gcc. (No sé lo que dicen las especificaciones acerca de esto, tal vez alguien va a llegar y publicarlo.)

En cuanto a gcc, también he intentado definir una sobrecarga diferente de bar antes foo, por lo que el símbolo es bar al menos definida:

void bar(float *) { 
} 

template<typename TT> void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 

void bar(int *) { 
} 

int main(int argc, char *argv[]) 
{ 
    std::vector<int*> vec; 
    foo(vec); 

    return 0; 
} 

pero gcc es todavía totalmente ciego a la sobrecarga de int *:

error: cannot convert int* to float* for argument 1 to void bar(float*)

parece gcc utilizará solamente aquellas funciones que se definen antes de la plantilla en sí.

Sin embargo, si elimina el especificador explícito :: y lo hace simplemente bar(tt), parece funcionar bien. La respuesta de Chris Dodd parece satisfactoria al explicar por qué esto es así.

+2

Sospecho que VC++ está mal aquí. Simplemente no molesta con la compilación en dos fases de plantillas y cosas dependientes/no dependientes en absoluto. Comeau está de acuerdo con GCC. - En cuanto a eliminar '::', supongo que deja la opción abierta de que se encuentre una 'barra' adecuada más tarde utilizando la búsqueda dependiente de argumentos (quizás se encuentre en el espacio de nombres al que pertenece T). – UncleBens

5

Desde la sección 14.7.2 de la especificación C++:

In an expression of the form:

postfix-expression (expression-listopt) 

where the postfix-expression is an unqualified-id but not a template-id , the unqualified-id denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.7.2.2).

desde ::b no no es un id no calificado, que es un nombre dependiente. Si elimina el ::, es un nombre no calificado y también un nombre dependiente. Para un nombre no dependiente, la búsqueda se produce en el punto de la declaración de la plantilla, no en la instanciación, y en ese momento no hay una declaración global de bar visible, por lo que se obtiene un error.

+0

El compilador siempre debe verificar que se haya declarado un nombre independientemente de si es o no dependiente. La búsqueda de nombres y la resolución de sobrecarga son dos cosas diferentes. El primero ocurre en la definición de la plantilla, el último en la instanciación. – Potatoswatter

+0

-1 No existe tal redacción en el estándar C++ 98. Hubo una corrección para C++ 03 pero no sé qué. Mi mejor estimación es que has buscado en algún borrador de C++ 0x. En el estándar C++ 98, es §14.6.2 que dice: "En una expresión de la forma * postfix-expression (expression-list) * donde * postfix-expression * es un * identificador *, el identificador denota un * dependent-name * si y solo si alguno de los expresiones en la * lista-expresión * es una expresión dependiente del tipo (14.6.2.2) ". Déjame ver ... Sí, tu fraseología está presente en §14. *** 6 ***. 2 del *** C++ 0x *** borrador. Uh, traté de eliminar downvote –

+0

@Alf de manera divertida, el borrador más reciente n3126 sí dice id-expression en lugar de identificador no calificado o identificador. Esto fue cambiado por [# 448] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#448). Pero no le hará nada al código del interrogador: aunque el nombre se volverá dependiente, la declaración no se encontrará porque ADL no hará nada por nombres calificados. –

1

Un nombre dependiente aún requiere una declaración de función. Cualquier declaración de función con ese nombre servirá, pero debe haber algo. ¿De qué otra forma podría el compilador eliminar la ambigüedad de una llamada de función sobrecargada de, por ejemplo, un objeto functor mal escrito? En otras palabras, al encontrar alguna función de ese nombre, verificando así que la sobrecarga en ese nombre es posible, instiga el proceso de resolución de sobrecarga en la búsqueda de nombres calificados (o no calificados).

// hello.cpp 

#include <vector> 

void bar(); // Comeau bails without this. 

template<typename TT> void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 

class Blah 
{ 
}; 

void bar(Blah *&) 
{ 
} 

int main(int argc, char *argv[]) 
{ 
    std::vector<Blah*> vec; 
    //foo(vec); - instanting it is certainly an error! 

    return 0; 
} 
+0

Bajo gcc 4.4.3, su código da el error '" demasiados argumentos para funcionar 'void bar()' "' para la línea 10 ':: bar (tt)'. Me interesa su idea de que la barra de símbolos debe estar parcialmente ligada a una familia de funciones sobrecargadas durante el análisis de la plantilla, pero creo que ese comportamiento es sorprendente si es verdadero, y al menos bajo gcc mi código funciona si el ':: 'se borra. (Este código es un ejemplo simplificado, para mi código real necesito el '::' para que nadie se moleste en sugerir eso, por favor.) –

+0

@Shawn: No sé qué decir. No probé con GCC, mi comentario se basa en haber leído esa parte del Estándar recientemente y haber probado Comeau. Sé que es ilegal sin ninguna declaración. No estoy seguro si GCC puede darle 'demasiados argumentos', tendría que retroceder y leer más de cerca. (No planee: v (.) – Potatoswatter

2

Esta fealdad trabaja con Intel C++ 11.0 y tal vez ilumina el punto de vista del compilador:

#include <vector> 
#include <iostream> 

// ********************* 
// forward declare in the global namespace a family of functions named bar 
// taking some argument whose type is still a matter of speculation 
// at this point 
template<class T> 
void bar(T x); 
// ********************* 

template<typename TT> 
void foo(std::vector<TT*> &vec) 
{ 
    TT *tt; 
    ::bar(tt); 
    vec.push_back(tt); 
} 

class Blah 
{ 
    public: 
}; 

void bar(Blah *x) 
{ 
    // I like output in my examples so I added this 
    std::cout << "Yoo hoo!" << std::endl; 
} 

// ********************** 
// Specialize bar<Blah*> 
template<> 
inline 
void bar<Blah*>(Blah *x) { ::bar(x); } 
// ********************** 

int main(int, char *) 
{ 
    std::vector<Blah*> vec; 
    foo(vec); 

    return 0; 
} 
Cuestiones relacionadas