2010-05-11 11 views
16
template<typename T> 
class Base 
{ 
protected: 
    Base() {} 
    T& get() { return t; } 
    T t; 
}; 

template<typename T> 
class Derived : public Base<T> 
{ 
public: 
    Base<T>::get;     // Line A 
    Base<T>::t;      // Line B 
    void foo() { t = 4; get(); } 
}; 

int main() { return 0; } 

Jeśli komentarz na linii A i B, to kod kompiluje grzywny pod Visual Studio 2008. Jednak kiedy mogę skompilować pod GCC 4.1 z linii A i B skomentował, otrzymuję te błędy:Dlaczego GCC potrzebuje dodatkowych deklaracji w szablonach, gdy VS tego nie robi?

In member function ‘void Derived::foo()’:
error: ‘t’ was not declared in this scope
error: there are no arguments to ‘get’ that depend on a template parameter, so a declaration of ‘get’ must be available

Dlaczego czy jeden kompilator wymaga linii A i B, a drugi nie? Czy istnieje sposób, aby to uprościć? Innymi słowy, jeśli klasy pochodne używają 20 rzeczy z klasy bazowej, muszę umieścić 20 linii deklaracji dla każdej klasy wywodzącej się z bazy! Czy istnieje sposób obejścia tego, który nie wymaga tak wielu deklaracji?

+6

Obowiązkowa Link do C++ FAQ: http://www.parashift.com/c++-faq- lite/templates.html # faq-35.19 – UncleBens

+4

Krótka odpowiedź: ponieważ gcc jest zgodny ze standardami i (co zaskakujące) Visual C++ nie jest? –

Odpowiedz

15

GCC ma rację w tym przypadku, a program Visual Studio błędnie akceptuje zniekształcony program. Zajrzyj do sekcji na temat Name lookup w podręczniku GCC. Parafrazując:

[T]he call to [ get() ] is not dependent on template arguments (there are no arguments that depend on the type T, and it is also not otherwise specified that the call should be in a [template-]dependent context). Thus a global declaration of such a function must be available, since the one in the base class is not visible until instantiation time.

Można obejść ten problem w jeden z trzech sposobów:

  • Deklaracje już używasz.
  • Base<T>::get()
  • this->get()

(Jest też czwarty sposób, jeśli chcą poddać się Dark Side:

Using the -fpermissive flag will also let the compiler accept the code, by marking all function calls for which no declaration is visible at the time of definition of the template for later lookup at instantiation time, as if it were a dependent call. We do not recommend using -fpermissive to work around invalid code, and it will also only catch cases where functions in base classes are called, not where variables in base classes are used (as in the example above).

Ale polecam przeciw temu, zarówno z powodu wymienione w instrukcji, a także z tego powodu, że Twój kod będzie nadal nieprawidłowy C++.)

+0

"omyłkowo" jest nieprawidłowe. Jeśli wyłączysz niestandardowe rozszerzenia w Visual C++ (które powinieneś zrobić, jeśli chcesz użyć go do pisania kodu przenośnego), ostrzeże cię, że twój kod używa tego rozszerzenia. –

+1

Nie, rozszerzenia te są lepsze niż bezgłośny "standard". – HardCoder

5

Problem nie dotyczy gcc, ale Visual Studio, w hich akceptuje kod, który nie jest zgodny ze standardem C++.

Została udzielona odpowiedź na tej stronie, więc będę krótko.

Standard wymaga szablonów być oceniane dwukrotnie:

  • raz w punkcie definicji: template <class T> struct Foo { void bar(); };
  • raz w punkcie instanciation: Foo<int> myFoo;

po raz pierwszy, wszyscy nazwy niepowiązane podlegają odliczeniu od kontekstu:

  • kompilator będzie rzucać się jeśli zapomniałeś interpunkcyjnych, odnosi się do nieznanych typów/metod/atrybuty
  • kompilator wybrać przeciążenie dla funkcji zaangażowanych w tym momencie

Ponieważ C składnia ++ jest niejednoznaczna, konieczne jest aby pomóc parserowi w tej fazie, i odpowiednio użyć słów kluczowych template i typename do dwuznaczności.

Niestety program Visual Studio nie jest zgodny i wdraża tylko drugą ocenę (w miejscu instancji).Korzyścią dla leniwych jest to, że można uciec bez tych dodatkowych template i typename słów kluczowych, wadą jest to, że kod jest źle sformułowane i nie przenośny ...

Teraz część zabawy:

void foo(int) { std::cout << "int" << std::endl; } 

template <class T> void tfoo(T i) { foo(i); } 

void foo(double) { std::cout << "double" << std::endl; } 

int main(int argc, char* argv[]) 
{ 
    double myDouble = 0.0; 
    tfoo(myDouble); 
    return 0; 
} 

Skompilowana z gcc, wyprowadza int.

Skompilowany za pomocą Visual Studio, wyprowadza double.

Problem? Każdy, kto ponownie użyje tego samego symbolu, co w kodzie szablonu w VS, może stanąć na wysokości zadania, jeśli jego symbol pojawi się pomiędzy włączeniem twojego kodu szablonu a chwilą, w której faktycznie używają kodu szablonu ... czyż nie jest to prawda? zabawny :/ ?

Teraz dla kodu:

template<typename T> 
class Derived : public Base<T> 
{ 
public: 
    void foo() { this->t = 4; this->get(); } 
}; 

this wskazuje, że nazwa jest zależna od następujących nazwa, to znaczy zależy T (co nie jest oczywiste, kiedy pojawi się symbol spokoju). Kompilator będzie więc oczekiwał na instancję i zobacz, czy dla konkretnego typu, który utworzysz, szablon Base<T> zawiera te metody. To nie jest obowiązkowe, ponieważ mogłem doskonale Specjalizujemy Base:

// It's non-sensical to instanciate a void value, 
template <> 
class Base<void> {}; 

I tak Derived<void> nie powinien skompilować;)

Powiązane problemy