tags:

views:

210

answers:

3
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; }

If I comment out lines A and B, this code compiles fine under Visual Studio 2008. Yet when I compile under GCC 4.1 with lines A and B commented, I get these errors:

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

Why would one compiler require lines A and B while the other doesn't? Is there a way to simplify this? In other words, if derived classes use 20 things from the base class, I have to put 20 lines of declarations for every class deriving from Base! Is there a way around this that doesn't require so many declarations?

+9  A: 

GCC is right in this case, and Visual Studio mistakenly accepts a malformed program. Have a look at the section on Name lookup in the GCC manual. Paraphrasing:

[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.

You can get around this in either of three ways:

  • The declarations you are already using.
  • Base<T>::get()
  • this->get()

(There is also a fourth way, if you want to succumb to the 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).

But I would recommend against that, both for the reason mentioned in the manual, and for the reason that your code will still be invalid C++.)

Thomas
"mistakenly" is incorrect. If you disable nonstandard extensions in Visual C++ (which you should do, if you want to use it to write portable code), it will warn you that your code uses this extension.
James McNellis
Accepting nonstandard code by default is the mistake, then.
Thomas
+1  A: 

The issue is not with gcc but with Visual Studio, which accepts code that is not conform to the C++ standard.

It's been answered through and through on this very site so I'll be brief.

The standard requires templates to be evaluated twice:

  • once at the point of definition: template <class T> struct Foo { void bar(); };
  • once at the point of instanciation: Foo<int> myFoo;

The first time, all the non-dependent names shall be deductible from the context:

  • the compiler will throw up if you forgot punctuation, refers to unknown types/methods/attributes
  • the compiler will select the overload for the functions involved at this point

Because C++ syntax is ambiguous, it is necessary to help the parser in this phase, and use the template and typename keywords appropriately to disambiguate manually.

Unfortunately, Visual Studio is not compliant, and only implement the second evaluation (at the point of instanciation). The advantage for the lazy is that you can get away without those extra template and typename keywords, the disadvantage is that your code is ill-formed and not portable...

Now for the fun part:

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;
}

Compiled with gcc, it outputs int.

Compiled with Visual Studio, it outputs double.

The issue ? Anyone reusing the same symbol you do in your template code in VS might step on your foot an mess up your implementation if their symbol appear between the inclusion of your template code and the moment they actually use the template code... isn't it funny :/ ?

Now, for your code:

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

The this indicates that the name following is a dependent name, ie it depends on T (which is not obvious when the symbol appears alone). The compiler will therefore wait for the instanciation and see if for the particular type you instantiate the template Base<T> does contain those methods. It's not mandatory, since I could perfectly specialize Base:

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

And thus Derived<void> should not compile ;)

Matthieu M.
A: 

If you are interested in details, see Standard Features Missing From VC++ 7.1. Part III: Two-Phase Name Lookup

Nemanja Trifunovic
I have found an amusing sentence in your article: "At one point I was surprised this was necessary, because Cleanup() was invoked with a dependent argument, and therefore I thought it should be dependant as well. However, it seems that compiler needs too see a function definition to consider it dependent. Invocation is not enough." Actually, you originally were right: The function name *is* dependent. But it's still not looked up in dependent base classes because the name is unqualified :) The primary reason is not non-dependency, but non-qualified names. :)
Johannes Schaub - litb
(actually, the fact that the name is dependent can confuse GCC during dependent name-lookup. See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43282 ).
Johannes Schaub - litb