Specifying template function to take containers.

Just came across this and am not very sure how it works, some help clarifying things would be great.

1
2
template <typename T>
typename T::value_type print(const T& c)


typename T::value_type

I suppose only containers have the member value_type? And the typename solidifies to the compiler that yes, T is a container type because of what followed the scope operator?

Then this function has the return type of typename T::value_type as well, but what if I wanted to change it?

1
2
template <typename T>
void print(const typename T::value_type& c)


This doesn't work as intended but isn't it the same thing?
Last edited on
In a template that we write, there are two kinds of names (identifiers) that could be used - dependant names and non- dependant names. A dependant name is a name that depends on a template parameter; a non- dependant name has the same meaning irrespective of what the template parameters are. For example:

1
2
3
4
5
template< typename T > void foo( T& x, int value )
{
    ++ T::static_member_variable ; // 'static_member_variable' is a dependant name
    ++value ; // 'value' is a non- dependant name
}


What a dependant name refers to could be something different for each different instantiation of the template. As a consequence, C++ templates are subject to "two-phase name lookup". When a template is initially parsed (before any instantiation takes place) the compiler looks up the non-dependent names. When a particular instantiation of the template takes place, the template parameters are known by then, and the compiler looks up dependent names.

During the first phase, the parser needs to know if a dependant name is the name of a type or the name of a non-type. By default, a dependant name is assumed to be the name of a non-type. The typename keyword before a dependant name disambiguates it to be the name of a type.

In template <typename T> typename T::value_type print(const T& c) ;, we clarify that the dependant name value_type is the name of a type.

In the this template,
1
2
3
4
5
template< typename T > 
typename T::value_type foo( const T& c, typename T::size_type sz )
{
    return T::number * sz ;
}

there are three dependant names - value_type which is the name of a type, size_type which too is the name of a type, and number which is the name of a non-type.

Usage could be something like this:
1
2
3
4
5
6
7
8
9
10
11
12
struct A
{
    using value_type = double ; // type
    using size_type = float ; // type
    static constexpr int number = 10 ; // non-type
};

int main ()
{
    A a ;
    foo( a, 2.345 ) ;
}
Did a little searching and value_type represents the type of the data within the container.

So can it be said that the addition of value_type is additional information to the compiler to narrow down the type of T?

1
2
3
4
5
6
7
8
                                    Unknown type
                                         |
                      template <typename T>
IDs T::value_type ->  typename T::value_type Foo(const T& c) { }
as a type, not a               |         |                
variable.                      |    T holds data type(s)  
                               |
                    Unknown type that contains value_type


and typename T::value_type is written immediately after template< typename T > to cement what T should be?
> and typename T::value_type is written immediately after
> template< typename T > to cement what T should be?

It just specifies the return type.

1
2
3
4
5
6
7
8
9
10
template < typename T > 
int foo( T a ) ; // int is the result type; foo<T>() returns an int 

template < typename T > 
T* bar( T& a ) ; // T* is the result type; bar<T>() returns a pointer to T

template < typename T > 
typename T::abcd foobar( T& a ) ; // T::abcd is the result type; 
// foobar<T>() returns an object of type T::abcd
// typename disambiguates that the dependant name is that of a type  


It does constrain the types with which foobar<>() could be instantiated,
the type T must have a nested type or type-alias abcd.

This too has the same requirement:
1
2
template < typename T > 
void baz( T& a, typename T::abcd* = nullptr  ) ; 


You might want to read up on
SFINAE: http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error
enable_if: http://www.drdobbs.com/windows/checking-concept-without-concepts-in-c/227500449

My brain's a little fried due to staying up too long, but what I'm understanding now is:

1) The compiler sees template <typename T> , it recognizes that T will be used later on for whatever that can validly fit T.

2) typename T::value_type comes along. It is a type in itself, but not the container type I'm expecting. Trying to pass a vector<int> to that would be like

- accessing T.
- finding value_type and expecting it to be of type vector<int>

which is of course wrong. The presence of typename T::value_type within the template's scope does give more information to what T should actually be. So using T var would generate the expected container type.

I tried to read up on the links, but it's kinda hard to push in atm. Am I getting close to understanding what's going on?
> 1) The compiler sees template <typename T> , it recognizes that

It recognizes that T is a type.


> 2) typename T::value_type comes along. It is a type in itself

Yes. The type T has a dependant name value_type, and that name is the name of some type.


> but not the container type I'm expecting.

Nothing is said which implies that T must be a container.
Just that this T has a T::value_type which too is a type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template < typename T > // T is a type
    typename T::value_type // T::value_type is a type
        front( T& c )
            { return c.front() ; } // T has a member 'front' which is callable

struct A
{
    using value_type = const int* ;
    value_type front() const { return &value ; }
    int value = 8 ;
};

#include <vector>
#include <iostream>

int main ()
{
    std::vector<int> cntr { 1234, -7, 345, 6789 } ;
    std::cout << front(cntr) << '\n' ; // T => std::vector<int>, T::value_type => int

    A a ;
    std::cout << front(a) << '\n' ; // T => A, T::value_type => const int*
}

http://ideone.com/v2Syiy
Okay I think I get it now. So when we start adding dependant names to the definition of a template, while T doesn't guarantee that it must be a container type, it must be a class type.

On top of that, the type that we pass to T must have the dependant name within its' scope as well.

typename T::dependant name is a type itself and can only accept types that it is capable of handling.

Is that right?
Yes.
Awesome. Thank you JLBorges for helping me out again.
Topic archived. No new replies allowed.