Understanding the Problem: Dependent Name Lookup in C++ Templates

When working with the Curiously Recurring Template Pattern (CRTP) or any form of template inheritance in C++, you might encounter compiler errors stating that base class members, types, or constants are "not declared in this scope".

In your code, the class Derived inherits from Base<Derived<Transform>, int, 4>. Because the base class template arguments depend on the template parameter Transform, Base is considered a dependent base class.

According to C++ template name lookup rules (specifically, two-phase lookup):

  • Phase 1: The compiler parses the template definition. Unqualified names (like type, size, and arr) are looked up immediately. However, the compiler does not look inside dependent base classes during this phase because they haven't been instantiated yet.
  • Phase 2: The template is instantiated. Only then does the compiler know the exact structure of the base class.

Because type, size, and arr are unqualified, the compiler looks for them in the scope of Derived and the global scope during Phase 1, fails to find them, and throws a compilation error.

The Solution: Making Names Dependent

To fix this, you must explicitly tell the compiler that these names depend on the template parameters. This forces the compiler to defer name lookup until Phase 2 (instantiation).

1. Accessing Member Variables with this->

For member variables like arr, you can prepend this->. Since this is always dependent in a class template, this->arr becomes a dependent name.

2. Accessing Types and Constants with using Declarations

For types and static constants, the cleanest approach is to define aliases using the using keyword. You must use the typename keyword when aliasing dependent types to inform the compiler that the dependent name refers to a type, not a member variable or function.

Corrected Implementation

Here is how you can correctly implement this behavior in your C++ code:

#include <iostream>
#include <algorithm>

template <class Derived, class T, size_t N>
class Base {
public:
    using type = T;
    static constexpr size_t size = N;

    Base() {
        size_t i = 0;
        std::generate_n(arr, N, [&i](){ return i++; });
    }

    void dosth() { impl::dosth(*static_cast<Derived*>(this)); }

protected:
    type arr[size];

private:
    struct impl : Derived {
        static void dosth(Derived& d) {
            void (Derived::*fn)() = &impl::dosth_impl;
            (d.*fn)();
        }
    };
};

template <class Transform>
class Derived : public Base<Derived<Transform>, int, 4> {
    // 1. Create an alias for the dependent base class for readability
    using BaseClass = Base<Derived<Transform>, int, 4>;

protected:
    // 2. Bring the dependent type and static constant into scope
    using type = typename BaseClass::type;
    using BaseClass::size;

    void dosth_impl()  {
        type out[size]; 
        
        // 3. Use 'this->' to access the dependent member variable 'arr'
        Transform::calc(out, this->arr);

        std::cout << out[0] << '\n';
        std::cout << out[1] << '\n';
        std::cout << out[2] << '\n';
        std::cout << out[3] << '\n';
    }
};

struct sqr {
    template <class T, size_t N>
    static void calc(T (&b)[N], const T (&a)[N]) {
        for (size_t i = 0; i < N; i++)
            b[i] = a[i] * a[i];
    } 
};

int main() {
    (Derived<sqr>{}).dosth(); // Outputs: 0, 1, 4, 9 as expected
}

Summary of Best Practices

  • Use this->member to access member variables inherited from template base classes.
  • Use using type = typename BaseClass::type; to bring base template types into local scope.
  • Use using BaseClass::static_member; to bring static constants or functions into local scope.