views:

636

answers:

4

I'm looking at some codes which makes heavy uses of templates. It compiles fine on GCC, but not on VS (tested on 2003 - 2010 beta 1), where it fails during syntax analysis. Unfortunately I don't know enough of the code structure to be able reduce the problem and reproduce it in only a few lines, so I can only guess at the cause. I'm hoping someone here can point me in the right direction.

We have

template< class UInt, typename IntT,
    bool is_signed = std::numeric_limits<IntT>::is_signed >
struct uii_ops_impl;

// ....

template<class UInt>
struct uii_ops_impl< UInt,
    typename make_signed<typename UInt::digit_type>::type, true >
{
    typedef UInt unbounded_int_type;
    typedef typename make_signed< typename unbounded_int_type::digit_type >::type
        integral_type;

    // ...

    static void add(unbounded_int_type& lhs, integral_type rhs);

    // ...
};

template<class UInt>
void uii_ops_impl<
    UInt, typename make_signed<typename UInt::digit_type>::type,
    true >::add(unbounded_int_type& lhs, integral_type rhs)
{
    // ....
}

When compiled on VS, the first error message (among many) it returns is

: error C2065: 'unbounded_int_type' : undeclared identifier

I mean, point at the typedef huh? :-S

EDIT:

It seems there's something to do with

typename make_signed<typename UInt::digit_type>::type

being used as a template parameter. Throughout the rest of the codes, similar typedefs being used in the member function parameter compiles fine. The only difference I can see so far is that none of the other cases have the above line as a template parameter. make_signed is from Boost.TypeTraits.

EDIT:

Okay, maybe that's not it, because the exact same thing is done in another file where it compiled fine. Hmm...

Bounty EDIT:

Okay, I think it's obvious at this point the problem is not actually where the compiler is complaining about. Only the two member functions definition at that particular point fail. It turns out that explicitly qualifying the parameter still doesn't compile. The only immediate solution is to define the function inline. That passes syntax analysis. However, when trying to instalize the template VS now fails because std::allocator<void> doesn't have a size_type member. Turns out VS have a specialization of std::allocator<T> for T=void that does not declare a size_type. I thought size_type is a required member of all allocators?

So the question now is, what could possibly foul up VS so much during syntax analysis that it complains about completely unrelated non-problem as errors, and how do you debug such codes?

p.s. For those that have too much time to spare, the code I'm trying to make work in VS is Kevin Sopp's mp_math in Boost's sandbox, which is based on libtommath.

A: 

Which line causes the first error?

Does the use of unbounded_int_type in this definition:

template<class UInt>
void uii_ops_impl<
    UInt, typename make_signed<typename UInt::digit_type>::type,
    true >::add(unbounded_int_type& lhs, integral_type rhs)
{
    // ....
}

need to be fully qualified:

template<class UInt>
void uii_ops_impl<
    UInt, typename make_signed<typename UInt::digit_type>::type,
    true >::add(uii_ops_impl<UInt, etc ,true>::unbounded_int_type& lhs, integral_type rhs)
{
    // ....
}

?

jon hanson
The error occurs at the definition. It is possible to get rid of the error by explicitly qualifying the parameters, but toy example test shows that VS doesn't require that.
KTC
+1  A: 

Here's something funny - this guy

ran into a bug with MSVC that's very similar to what you're seeing - except that using a typedef worked around the problem for him.

I still don't know what to make of the problems he ran into (or that you're running into). As you say, the small snippet you posted doesn't repro the error (given a simple make_signed<> template that lets make_signed<>::type compilable).

Michael Burr
I actually saw that question earlier when I was searching for possible reasons I'm having problems with mine. His might have something to do with Argument Dependent Lookup. A qualified argument does not activate ADL, whereas an unqualified argument does.
KTC
Except that the code in the other question won't be doing ADL (or it shoudln't) - the `SetToFirst()` function isn't being called, it's being defined. Anyway, I wasn't sure that it would help you in any way (or even if it was really related), but... it struck me as at least interesting.
Michael Burr
+5  A: 

I think this can be caused by a few circumstances

  • unbounded_int_type is a non-dependent type (defined at 14.6.2.1)
  • Its declaration appears in the class template. Because it's non-dependent, its name has to be resolved to a declaration at the time the member function is defined.

I suspect that Visual C++ is not able to do this lookup, and errors out instead. As someone else mentions, you can explicitly qualify the type-names in the member function definition. The types are then dependent, and this will trigger the compiler's mechanism to delay name lookup until instantiation.

template<class UInt>
void uii_ops_impl<
    UInt, typename make_signed<typename UInt::digit_type>::type,
    true >::add(typename
                /* repeat-that-beast uii_ops...true> here */
                   ::unbounded_int_type& lhs, 
                typename /* and here too */::integral_type rhs)
{
    // ....
}
Johannes Schaub - litb
It seems to me that the types for the argments for `add()` should follow the lookup rules in 3.4.1(8) - Unqualified name lookup. The rule that should apply (I think) is "shall be a member of class X or be a member of a base class of X", so the compiler should be able to resolve the name. As KTC indicated, the name lookup succeeds in the pared-down example; it's when some unspecified other stuff is in the mix that the problem crops up.
Michael Burr
I wasn't sure what example he used for the check. Something like `struct X { typedef int Ty; void f(Ty); }; void X::f(Ty) { }` could have worked, but of course is a much different situation :)
Johannes Schaub - litb
In the other code you link to in your answer, the same situation as we have happens, but it still seems to work for them. Maybe that's because in this case, a partial specialization is involved.
Johannes Schaub - litb
A: 

You must help the compiler a bit on this one. You have to use the typename keyword because you have a qualified name that refers to a type and depends on a template parameter.

Think in this terms how can you be sure that unbounded_int_type::digit_type is a type? It depends on which type is unbounded_int_type. So you remove the ambiguity by adding the typename keyword.

template< class UInt, typename IntT,
bool is_signed = std::numeric_limits<IntT>::is_signed >
struct uii_ops_impl;

// ....

template <class T>
struct make_signed
{
    typedef T type;
};

template<class UInt>
struct uii_ops_impl< UInt,
    typename make_signed<typename UInt::digit_type>::type, true >
{
    typedef UInt unbounded_int_type;
    typedef typename make_signed< typename unbounded_int_type::digit_type >::type
     integral_type;

    // ...

    static void add(unbounded_int_type& lhs, integral_type rhs);

    // ...
};

template<class UInt>
void uii_ops_impl<
UInt, typename make_signed<typename UInt::digit_type>::type,
true >::add(unbounded_int_type& lhs, integral_type rhs)
{
    // ....
}

The only change is here - I added the typename keyword.

typedef
typename make_signed< typename unbounded_int_type::digit_type >::type          integral_type;
smink
What you posted is exactly what's already there....
KTC