I'm studying about iterator and I found some source code on github.
I realize what this code do but cannot find how.
template <class T>
struct _has_iterator_category
{
private:
struct _two { char _lx; char _lxx; };
template <class U> static _two _test(...);
template <class U> static char _test(typename U::iterator_category * = 0);
public:
static const bool value = sizeof(_test<T>(0)) == 1;
};
I think this code check if T
has iterator_category, but I cannot figure few things about how and why this works.
- Why this code use two template? what
class U
template does? - Is
_test(...)
function or constructor? And what is(...)
means?
2-1. If_test
is function, is this code doing function overloading? then how can be overloaded with different return type?
2-2. If_test
is constructor, then ischar
a class in c ? - What does
* =
operator do in(typename U::iterator_category * = 0)
? Is it multiplyingiterator_category
or make 0 ofiterator_category
pointer? - what
sizeof(_test<T>(0)) == 1;
means? Is it return true ifsizeof(_test<T>(0))
is 1?
I searched a lot of document for iterator_traits
and other things, but failed to interpret this code.
CodePudding user response:
First thing first, this code is completely interpretable on it's own, if you know C . No documentation on external components is required. It's not depending on anything. You have asked questions which suggest some gaps in basic C syntax understanding.
1. Template definition _test
is a template member of class template _has_iterator_category
. It's a template defined within template, so even if you instantiate _has_iterator_category
, you still have to instantiate _test
later, it got a separate template parameter.
2. Technically, it's neither. Because a class template isn't a type and a function template, which _test
is, is not a function.
Constructor's name always matched the most nested enclosing class scope, i.e. _has_iterator_category
in here. _has_iterator_category
doesn't have a constructor declared.
It's a template of function. There are two templates, for different arguments, with different argument type. If both templates can be instantiated through successful substitution of U with concrete type, the function is overloaded.
3. It's not operator * =
, operators cannot have a whitespace in them. It's *
and =
. This is a nameless version of argument list which could be written otherwise:
template <class U> static char _test(typename U::iterator_category *arg = 0);
= 0
is default value of function parameter arg
. As arg
is not being used in this context, its name can be skipped.
The single parameter of function's signature got type U::iterator_category *
. typename
is a keyword required by most but recent C standards for a nested type dependant on template parameter. This assumes that U must have a nested type
iterator_category
. Otherwise the substitution of template parameters would fail.
template <class U> static _two _test(...);
Here function signature is "variadic". It means that function may take any number of arguments after substitution of template parameters. Just like printf
.
4. sizeof(_test<T>(0)) == 1
equals to true
if size of _test<T>(0)
result is equal to 1.
The whole construction is a form of rule known as SFINAE - Substitution Failure Is Not An Error. Even if compiler fails to substitute one candidate, it would still try other candidates. The error would diagnosed if all options are exhausted.
In this case the expression sizeof(_test<T>(0))
, which attempts to substitute U
with T
. It's the reason why _test
is made into a nested template. The class is valid, but now we check the function.
If type-id T::iterator_category
is valid, then substitution is successful. _test(...)
can be successful, but then we go to overload choice rules.
A variadic argument always implies type conversion, so there is no ambiguity and _test(...)
will be discarded.
If T::iterator_category
is not a valid type, _two _test(...)
is the only instance of _test()
.
Assuming that sizeof(char)
equals to 1, the constant value
is initialized with true
if return value of expression would be _test<T>(0)
got same size as char
. Which is only true if T::iterator_category
exists.
Essentially this constructs checks, if class T
contains nested type T::iterator_category
in somewhat clumsy and outdated ways. But it is compatible with very early C standards as it doesn't use nullptr
or <type_traits>
header.