Skip to content

85 Advanced The Hundred

The Member That Wasn't There

the base class declares it, plain as day

#include <iostream>

template <class T> struct Storage {
    T value{};
};

template <class T> struct Wrapper : Storage<T> {
    void set(T v) { value = v; }
    T get() { return value; }
};

int main() {
    Wrapper<int> w;
    w.set(42);
    std::cout << w.get() << "\n";
}

Run it. What does it print?

Answer

Nothing — it doesn't even compile: error: 'value' was not declared in this scope. The member the whole class is built on is, apparently, not there.

Why

Storage<T> is a dependent base — which class it actually is depends on T, and someone could later specialize it: a Storage<bool> with no value at all is fair game. So the compiler refuses to assume: while parsing Wrapper, unqualified lookup simply does not search dependent bases, finds no value anywhere, and rejects the code before Wrapper<int> is ever instantiated. Writing this->value changes everything — this makes the expression dependent, so lookup is deferred to instantiation time, when Storage<int> is a real class with a real value. With a non-template base, none of this applies: unqualified value just works.

The fix

Any of these three, each saying "trust me, it's in the base":

void set(T v) { this->value = v; }       // defer the lookup to instantiation
T get() { return Storage<T>::value; }    // or name the base explicitly
using Storage<T>::value;                 // or import it once, in the class body

Takeaway: inside a template, members of a dependent base are invisible to unqualified lookup — reach for them with this->.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo — prints 42 (the shipped main.cpp uses the fix); add -DSHOW_BUG to get the error above.

Open in Compiler Explorer ↗ Open SHOW_BUG variant ↗ Quiz this entry