3

Why would some compilers insist on qualifying members public members of template base class while the not requiring the same for non-template class? Please look at the following code listings:

Template class:

#include <iostream>

using namespace std;

template <class T>
class TestImpl {
public: // It wont make a difference even if we use a protected access specifier here
    size_t vval_;
    TestImpl(size_t val = 0) : vval_(val) { }
};

template <class T>
class Test : public TestImpl<T> {
public:
    Test(size_t val) : TestImpl<T>(val) {
        cout << "vval_ : " << vval_ << endl; // Error: vval_ was not declared in this scope
        //! cout << "vval_ : " << TestImpl<T>::vval_ << endl; // this works, obviously
    }
};

int main() {
    Test<int> test1(7);

    return 0;
}

Non-template class:

#include <iostream>

using namespace std;

class TestImpl {
public: // It wont make a difference even if we use a protected access specifier here
    TestImpl(size_t val = 0) : vval_(val) {}
    size_t vval_;
};

class Test : public TestImpl {
public:
    Test(size_t val) : TestImpl(val) {
        cout << "vval_ : " << vval_ << endl;
    }
};

int main() {
    Test test1(7);

    return 0;
}

The significant difference between the above code listings is that while the first listing uses template classes, the second one doesn't.

Now, both listings will compile fine with Microsoft's Visual Studio Compiler (cl) but the first listing WONT compile with both the Digital Mars Compiler (dmc) and Minimalist GNU for Windows (MinGW - g++) compiler. I will get an error like "vval_ was not declared in the scope" - an error I obviously understand what it means.

If I qualify access to the TestImpl's public variable vval_ using TestImpl<T>::vval_ the code works. In the second listing, the compilers do not complain when the derived class accesses the base class' vval_ variable without qualifying it.

With regard to the two compilers and possibly others, my question would be why I should be able to directly access (without qualifying) vval_ variable directly from a non-template class inheriting from a non-template class, while I cant do the same from a template class inheriting from a template class?

John Gathogo
  • 4,495
  • 3
  • 32
  • 48
  • 2
    You can also qualify the `vval_` thus: `this->vval_`. – Robᵩ Oct 18 '11 at 13:31
  • 2
    http://www.parashift.com/c++-faq-lite/templates.html#faq-35.19 – visitor Oct 18 '11 at 13:35
  • @Rob: this->vval_ works in the derived class. When using "implemented in terms of a" kind of inheritance, where significant part of your implementation is in the base claas, it just feels unwieldy having to do this-> every place. Thanks – John Gathogo Oct 18 '11 at 13:41
  • @visitor: revealing read – John Gathogo Oct 18 '11 at 13:42
  • @JohnGathogo: If the identifier is dependent on the type arguments, but that dependency is not explicit in the code, you will need to explicitly state that dependency, and `this->` will start not looking so bad... – David Rodríguez - dribeas Oct 18 '11 at 13:59
  • This happens because Visual Studio's compiler (MSVC) does not perform the two-phase name lookup by default. You can change that behavior with a compiler switch. There are 4 solutions to this problem: **1)** Use the prefix `TestImpl::vval_`, **2)** Use the prefix `this->vval_`, **3)** Add a statement `using TestImpl::vval_`, **4)** Use a global compiler switch that enables the permissive mode. The pros & cons of these solutions are described in https://stackoverflow.com/questions/50321788/a-better-way-to-avoid-public-member-invisibility-and-source-code-bloat-repetitio – George Robinson May 14 '18 at 14:06

3 Answers3

6

You have to qualify vval_ with the TestImpl<T> to tell the compiler that it depends on the actual type of T in Test<T> (there might be some partial/explicit specializations of TestImpl<T> declared before the definition of Test<T> and it's instantiation that will change the meaning of vval_ in that context. In order to make the compiler aware of that, you have to tell that vval_ is (template parameter) dependent.

See also http://gcc.gnu.org/onlinedocs/gcc/Name-lookup.html

MichalR
  • 263
  • 1
  • 6
3

MSVC (Microsoft ...) has never been standard compliant when it comes to template code, so he's the odd one out :)

The problem is that templates are parsed in two phases:

  • The first phase is executed when the template is parsed, and any identifier that does not depend (explicitly) on template parameters should be resolved
  • The second phase is executed when the template is instanciated

In your case, the first phase fails because vval_ does not depend explicitly on a template parameter (is not a dependant name), thus it should be available.

A simple remedy is to qualify vval_, usually with this->, to mark it as dependent.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • It's not that bad actually, the only missing thing (required by standard) is two-phase name lookup. And it's not required to be a two-phase parsing, e.g. one can imagine compiler implementation which will just remember context at the point of template definition and use it for proper name lookup at the point of instantiation. – Konstantin Oznobihin Oct 18 '11 at 14:17
  • @KonstantinOznobihin: I disagree with the *not that bad*. Name lookup is akin to overload resolution: what would say of a compiler that would always pick the last possible overload ? I understand they might wish not to pester the user with dependent names issues for usability, but you can do so without breaking the look-up :x – Matthieu M. Oct 18 '11 at 14:23
  • @KonstantinOznobihin: Well... the standard determines that a dependent type in a template context must be preceded by the `typename` keyword (or dependent templates with `template` keyword) and VS ignores that requirement, making it quite easy to write non-portable code... It all depends on how you look at it – David Rodríguez - dribeas Oct 18 '11 at 14:29
  • Still, I think, absence of two-phase name-lookup hardly can be treated as 'has never been standard compliant when it comes to template code'. And when we come to a name-lookup, do you have any examples of good code which does rely on two-phase name-lookup to be performed? It's not that great to have this feature missing indeed, but your analogy with overload resolution is just incorrect. – Konstantin Oznobihin Oct 18 '11 at 14:32
  • @KonstantinOznobihin: I don't remember if it was good, I do remember it was portable, and getting a fix in just for the sake of MSVC's brokenness is not appealing :/ – Matthieu M. Oct 18 '11 at 14:35
  • @DavidRodríguez-dribeas: still it doesn't miss really important features (or does it?), and this non-portability is too easy to fix to talk about seriously. E.g. instead of 'compile on MSVC -> get error -> fix -> compile on GCC -> no error' you'll get 'compile on MSVC -> no error -> compile on GCC -> get error -> fix', exactly the same number of steps and it's not something you'll build your architecutre upon just to find it's non-portable and you'll have to reimplement everything from scratch. – Konstantin Oznobihin Oct 18 '11 at 14:39
  • 2
    @KonstantinOznobihin: consider: `int x = 10; template struct base { static int x; }; template int base::x = 1; template struct derived : base { void foo() { std::cout << x; }` I believe that according to the standard (I would have to check), `x` in `derived::foo()` refers to `::x` not `base::x`. Granted I have never found that issue in real code. – David Rodríguez - dribeas Oct 18 '11 at 14:58
  • @DavidRodríguez-dribeas: yes, it would refer to ::x, that's how two-phase lookup works. And it's a good example of how it helps in alleviating brittle base class problem. Still, do you think it's a good practice to rely on this to work for template classes while it doesn't for non-template ones? I guess your answer is no since you was not really sure it will refer to ::x and not base::x. – Konstantin Oznobihin Oct 18 '11 at 16:27
  • @KonstantinOznobihin: it's not a matter of being a good practice, it's a matter of silent miscompilation that leaves blood stains on the walls once you stop hitting your head trying to understand the differences between your compilers behavior. – Matthieu M. Oct 18 '11 at 17:20
  • @KonstantinOznobihin: Just think that in your base class you have a member `x` and you compile and it seems to work and you are happy and all. Then you move to a different compiler, and it just happens that in an enclosing namespace or a namespace that is brought by a using declaration there is a different `x`, it compiles perfectly but it provides a different result. The problem is not *depending* on the correct behavior which I wouldn't (I would fully qualify in code), but depending on the broken behavior in VS *unknowingly* as the OP expected, and then having the code break. – David Rodríguez - dribeas Oct 18 '11 at 20:52
  • @DavidRodríguez-dribeas: I don't argue one can get in a trouble due to difference in compilers behaviour or that it's ok for a compiler to violate the standard. It's just that MSVC compiler templates support is not that bad really. And this particuar issue being somewhat nasty, still far from being a showstopper. Afterall, it's a bit naive to believe one can make non-trivial project portable without knowing compilers specifics, without automated testing and not adhering to best practices, but just by following the standard. – Konstantin Oznobihin Oct 19 '11 at 05:46
  • @All: Thanks guys. I have to admit, this discussion is quite deep, healthy and enlightening. These are all good points but you have to admit that having some piece of code compile on a particular compiler and failing miserably on another is not necessarily fun. But on a positive note, coming across such showstoppers and glitches makes me appreciate a couple of things: standards, best practices, testing, not to mention a lot of things I learn as I am pulling my hairs trying to unravel the mysteries. Once again, thanks – John Gathogo Oct 19 '11 at 06:37
2

The problem you are facing is that for the compiler vval_ is not a dependent name, so it will try to look it up before the actual instantiation of the template with the type. At that point, the base type is not yet known to the compiler [*], and thus it does not consider the templated bases. Visual Studio does not perform the two phase lookup, and thus this is not required there.

The solution is transforming the identifier into a dependent identifier, which can be done in one of multiple ways. The simplest and recommended would be using this (as in this->vval_). By adding the explicit this, the compiler knows that vval_ can differ depending on the template arguments, it is now a dependent name, and it postpones lookup to the second phase (after argument substitution).

Alternatively, you can qualify the type that the identifier belongs to, as @mrozenau suggests, by using TestImpl<T>::vval_. Again that makes the identifier dependent on the template argument T and lookup is postponed. While both of them serve the ultimate purpose of postponing the lookup to a later time, this second approach has the extra side effect that dynamic dispatch will be disabled. In this particular case it does not matter, but if vval_ was actually a virtual function then this->f() would call the final overrider, while TestImpl<T>::f() would execute the overrider present in TestImpl<T>.

[*] During the first phase verification of templates, before the arguments are substituted into the template, the base type is not yet known. The reason for this is that different sets of arguments might trigger the selection of different specializations of the base template.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489