Nazwy zależne od typów

Nazwy zależne od typów#

Gramatyka języka C++ nie jest niezależna od kontekstu. Aby sparsować np. definicję funkcji, potrzebna jest znajomość kontekstu, w którym funkcja jest definiowana.

Przykład problemu:

template <typename T>
auto dependent_name_context1(int x)
{
    auto value = T::A(x);
    
    return value;
}

Standard C++ rozwiązuje problem przyjmując założenie, że dowolna nazwa, która jest zależna od parametru szablonu odnosi się do zmiennej, funkcji lub obiektu.

struct S1
{
    static int A(int v) { return v * v; }
};

auto value2 = dependent_name_context1<S1>(10); // OK - T::A(x) was parsed as a function call

Słowo kluczowe typename umożliwia określenie, że dany symbol (identyfikator) występujący w kodzie szablonu i zależny od parametru szablonu jest typem, np. typem zagnieżdżonym – zdefiniowanym wewnątrz klasy.

struct S2
{
    struct A
    {
        int x;
        
        A(int x) : x{x}
        {}
    };
};

template <typename T>
auto dependent_name_context2(int x)
{
    auto value = typename T::A(x); // hint for a compiler that A is a type
    
    return value;
}

auto value = dependent_name_context2<S2>(10); // OK - T::A was parsed as a nested type

Przykład użycia słowa kluczowego typename dla zagnieżdżonych typów definiowanych w kontenerach:

template <class T> 
class Vector 
{      
public:
    using value_type = T;
    using iterator = T*;
    using const_iterator = const T*;    

    // ...
    // rest of implementation
};

template <typename Container>
typename Container::value_type sum(const Container& container)
{
    using result_type = typename Container::value_type;

    result_type result{};

    for(typename Container::const_iterator it = container.begin(); it != container.end(); ++it)
        result += *it;

    return result;
}

Podobne problemy dotyczą również nazw zależnych od parametrów szablonu i odwołujących się do zagnieżdżonych definicji innych szablonów:

struct S1 { static constexpr int A = 0; }; // S1::A is an object
struct S2 { template<int N> static void A(int) {} }; // S2::A is a function template
struct S3 { template<int N> struct A {}; }; // S3::A is a class template
int x;

template<class T>
void foo() 
{
    T::A < 0 > (x); // if T::A is an object, this is a pair of comparisons;                    
                    // if T::A is a typename, this is a syntax error;                        
                    // if T::A is a function template, this is a function call;        
                    // if T::A is a class or alias template, this is a declaration.
}

foo<S1>(); // OK

Aby określić, że dany symbol zależny od parametru szablonu to szablon funkcji piszemy:

template <typename T>
voi foo()
{
    T::template A<0>();
}

Aby określić, że dany symbol zależny od parametru szablonu to szablon klasy piszemy:

template <typename T>
voi foo()
{
    typename T::template A<0>();
}