views:

68

answers:

2

This code fragment:

namespace ns
{
    struct last;

    struct first
    {
        typedef last next;
    };

    template <typename T>
    struct chain
    {
        chain<typename T::next> next;
    };

    template <>
    struct chain<last>
    {
    };
}

using namespace ns;

template <typename T>
void f(const T& x)          // #1
{
    f(x.next);
}

void f(const chain<last>&)  // #2
{
}

int main()
{
    f(chain<first>());
}

gives the following error on Comeau, and a very similiar error on GCC:

"ComeauTest.c", line 27: error: class "ns::chain<ns::last>" has no member "next"
    f(x.next);
        ^
          detected during:
            instantiation of "void f(const T &) [with T=ns::chain<ns::last>]"
                      at line 27
            instantiation of "void f(const T &) [with T=ns::chain<ns::first>]"
                      at line 36

It does compile, however, if either #2 is defined ahead of #1, or if last is declared outside of ns.

Any explanation for this?

+6  A: 

Case 1)

template <typename T>
void f(const T& x)          // #1
{
    f(x.next); //where's f ??
}

void f(const chain<last>&)  // #2
{
}

You need to make sure that #2 is a template specialization of #1 by specifying template<> above void f(const chain<last>&) // #2

Without template<> void f(const chain<last>&) would be interpreted as an overload of f. So a call to f(x.next); would be ill formed because of the missing declaration of void f(const chain<last>&).

Adding a declaration of the overload above the function template would make your code compile.

Solutions:

1)

template <typename T>
void f(const T& x)          // #1
{
    f(x.next); //hmm specialized version down there.
}

template<>
void f(const chain<last>&)  // #2
{
}

2)

void f(const chain<last>&); // #0

template <typename T>
void f(const T& x)          // #1
{
    f(x.next); //hmm I can see #0, call #2
}

void f(const chain<last>&)  // #2
{
}

Case 2)

void f(const chain<last>&)  // #2
{
}

template <typename T>
void f(const T& x)          // #1
{
    f(x.next); // found!!
}
Prasoon Saurav
But the POI of `#1` is right after `main`, where `#2` is visible.
uj2
@uj2 : `#2` is not visible at the point where it is called.
Prasoon Saurav
@Prasoon Saurav: My confusion (and maybe uj2's) is that the call to f in main is selecting the templated f rather than the non-templated f. At the call in `main`, both functions are visible, right? Why is this happening?
JoshD
@JoshD: There are complicated rules about which overloads are considered. My guess is, informally spoken, if the compiler can decide at the point of definition what to call, then it won't change that decision at the point of instantiation.
UncleBens
+1  A: 

Given

template <typename T>
void f(const T& x)          // #1
{
    f(x.next);
}

void f(const chain<last>&)  // #2
{
}

... the call to f in the body of the first can not ever call the second f, because the second f is not visible at that point.

So if you get into the first f, then it will recurse down to first error. :-) I'm talking about compile time recursion here, as long as next is of a type that f is not yet instantiated for.

And the call in main, ...

int main()
{
    f(chain<first>());
}

... necessarily calls the first f, since chain<first> does not match the argument type of the second f.

This results in recursive call f( arg of type chain<last> ). And when trying to instantiate f for argument type chain<last> you get an error, since there's no next attribute in chain<last>.

Regarding the code apparently compiling OK when placing the declaration of last in the global namespace, I don't know. Are you sure? Note: I haven't tried any of this with a real compiler.

Cheers & hth.,

Alf P. Steinbach