Gankidi Vasu
Gankidi Vasu

Reputation: 61

Template Aliases - question (tour of c++)

I was going through the template Aliases from Tour of C++. I couldn't understand the below code and how to use it?

template<typename T>
class Vector {
public:
   using value_type = T; 
}

Here he is using the value_type as type alias for typename 'T', Why can't we just use the typename T, since we can pass any type to template(i.e. class Vector). What is the need to have alias for template?

template<typename C>
using Value_type = typename C::value_type;

Here how is value_type in the scope of C, i.e. how can we reference value_type with type 'C', since it is inside class "vector"? does 'value_type' here mean its a Vector? and 'Value_type' mean int::Vector or string::Vector etc..?

template<typename Container>
void algo(Container &c)
{
  Vector<Value_type<Container>> vec;
}

How are these three pieces linked?

Upvotes: 1

Views: 138

Answers (1)

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122458

Why a member alias?

Consider you use an instantiation of Vector in some other generic code:

template <typename U>
void foo(const U& u);

Vector<int> v;
foo(v);

Then inside foo we would need to go through some hoops to find out that T is int. foo only knows about U which is Vector<int>. If Vector<int> has a value_type alias then it is much simpler to access that:

template <typename U>
void foo(const U& u) {
    using value_type = typename U::value_type;
    //...
}

Now foo does not need to care that U::value_type actually was the T parameter to Vector and is also fine with a type that is not an instantiation of a template:

struct OtherVector {
    using value_type = int;
};
OtherVector ov;
foo(ov);

No member alias: "some hoops"

Using a member alias is the simple way. For the sake of completeness I want to show the complicated way that lets foo infer T from Vector<T>. You'll have to bear with me...

First, note that function templates cannot be partially specialized. Thats why I introduce a level of indirection:

template <typename U>
struct foo_impl {
    void operator()(const U& u) {
        std::cout << "Hello \n";
    } 
};

template <typename U>
void foo(const U& u) { foo_impl<U>{}(u); }

Caller will call foo and in the background we can mess around with foo_impl. For example we can add a partial specilization for Vector<T> where we can directly have access to T:

template <typename T>
struct foo_impl< Vector<T> > {
    void operator()(const Vector<T>& v) {
        std::cout << "Hello Vector<T>\n";
        if constexpr (std::is_same_v<int,T>) {
            std::cout << "T == int\n";
        }
    }
};

Live Example.

However, consider what that means for foo. The foo above is fine with any type that has a value_type alias. Now we have a rather boring general definition (prints "Hello") and a specialization for Vector<T> (that prints more when T==int). Thats quite a restriction if we want foo to also work with a

 template <typename T>
 struct Bar {};

What can we do? We can provide a more generic specialization that matches also instantiations of Bar:

template <template<class> class A, class T>
struct foo_impl< A<T> > {
    void operator()(const A<T>& v) {
        std::cout << "Hello A<T>\n";
        if constexpr (std::is_same_v<int,T>) {
            std::cout << "T == int\n";  // We know what T is when a Vector<T> is passed !!
        }
    }
};

Live Example

It is using a template tempalte parameter to match any instantiation of a template with a single template parameter.

Are we fine now? Unfortunately no, because suppose we want to get a "value type" from this:

template <typename A,typename B>
struct Moo {};

Suppose by convention the first parameter, A, is the value type we are looking for. Then we need to add an even more generic specialization:

template <template<class...> class A, typename T,typename... Others>
struct foo_impl< A<T,Others...> > {
    void operator()(const A<T,Others...>& v) {
        std::cout << "Hello A<T>\n";
        if constexpr (std::is_same_v<int,T>) {
            std::cout << "T == int\n"; 
        }
    }
};

Live Example

This will match an instantiation of a template with any number of type parameters and will detect the first template parameter to be T.

Are we fine yet? Unfortunately: Not at all. The above does not match a

template <typename A, int x>
struct Omg {};

Because it has a non-type template parameter. We could also fix that, but lets stop here. Requiring that a value type is always the first parameter is too restrictive anyhow.

TL;DR

What we actually want is to make foo work with any type that has an associated "value type". And the simple way to do that is that any type that should be passed to foo to supply a member alias called value_type.

Upvotes: 5

Related Questions