ambiguous overload when used with derived classes?

S

sebastian

I've simplified the situation quite a bit, but essentially I have
something like this:

Code:
struct foo
{      };

void
bar( foo const & lhs, foo const & rhs )
{      }

template < typename Lhs >
void
bar( Lhs const & lhs, foo const & rhs )
{      }

template < typename Rhs >
void
bar( foo const & lhs, Rhs const & rhs )
{      }

struct baz : foo
{      };

int
main( void )
{
      bar( foo( ), foo( ) ); // fine
      bar( 1024, foo( ) ); // fine
      bar( foo( ), 1024 ); // fine
      bar( baz( ), baz( ) ); // error: ambiguous overload
      bar( 1024, baz( ) ); // fine
      bar( baz( ), 1024 ); // fine
}

I thought that the compiler would deduce bar( foo const &, foo const
& ). Is there some way around this problem?
 
G

gnuyuva

int
main( void )
{
bar( baz( ), baz( ) ); // error: ambiguous overload

The code generated here would be:
template<> void bar(baz const&, foo const&);
AND
template<> void bar(foo const&, baz const&);

Hence the call is ambiguous.
I thought that the compiler would deduce bar( foo const &, foo const
& ). Is there some way around this problem?

What you can do is:

template <typename T1, typename T2>
void bar(T1 const&, T2 const&)
{
// first check for equality of T1 and T2.
ASSERT_COMPILE( is_equal<T1, T2>::yes );
// now check whether T1 is derived from foo.
ASSERT_COMPILE( super_sub<foo, T1>::yes );

...
}

// this construct will say whether two types are equal.
template <typename T1, typename T2>
struct is_equal { enum {yes = 0}; };

template <typename T>
struct is_equal<T, T> { enum { yes = 1 }; };


// this construct will say whether T2 is derived from T1.
// the second assertion is pretty difficult. // use SFINALE
template <typename T1, typename T2>
struct super_sub
{
typedef char one;
struct two { char _two[2]; };

static one deduce(T1*);
static two deduce(...);

typedef T2* T2_ptr;
enum { yes = ( sizeof(one) == sizeof( deduce( T2_ptr() ) ) ) };
};

Search for techniques like type-deduction for more details.
 
J

James Kanze

I've simplified the situation quite a bit, but essentially I
have something like this:
Code:
[/QUOTE]

struct foo
{      };[/QUOTE]
[QUOTE]
void
bar( foo const & lhs, foo const & rhs )
{      }[/QUOTE]
[QUOTE]
template < typename Lhs >
void
bar( Lhs const & lhs, foo const & rhs )
{      }[/QUOTE]
[QUOTE]
template < typename Rhs >
void
bar( foo const & lhs, Rhs const & rhs )
{      }[/QUOTE]
[QUOTE]
struct baz : foo
{      };[/QUOTE]
[QUOTE]
int
main( void )
{
bar( foo( ), foo( ) ); // fine
bar( 1024, foo( ) ); // fine
bar( foo( ), 1024 ); // fine
bar( baz( ), baz( ) ); // error: ambiguous overload
bar( 1024, baz( ) ); // fine
bar( baz( ), 1024 ); // fine
} [QUOTE]

I thought that the compiler would deduce bar( foo const &, foo
const & ).

Why? The compiler has to choose between three functions:

void bar( foo const&, foo const& ) ;
void bar( baz cosnt&, foo const& ) ;
void bar( foo const&, baz const& ) ;

For the first argument, the second function is the best match,
and for the second, the last function. How can the compiler
decide?
Is there some way around this problem?

What problem are you trying to solve?

If nothing else:

template< typename T >
void
bar( T const& lhs, T const& rhs )
{
bar( static_cast< foo const& >( lhs ),
static_cast< foo const& >( rhs ) ) ;
}

will force the non-template version to be called anytime both
arguments have the same type.
 
S

sebastian

How can the compiler decide?

In my opinion the best candidate would be the first function since baz
'is-a' foo. At the very worst, it would force the user to define a
specialization to narrow things down further. As it currently stands,
since the language's ranking system cannot resolve the overload
sensibly and so there is no real solution!

'bar' is actually an overloaded operator, so that really won't work in
this situation.

Essentially, I have a forwarding mechanism where user-defined types
can be used with the library via certain specializations, and I wanted
to allow these types on either side of an expression. I'll probably
end up going with the third function signature since it covers most
bases, until (or unless) I find a satisfactory solution.

Thank you all for the suggestions.

Cheers,
- Sebastian
 
G

Greg Herlihy

Code:
struct foo
{      };
void
bar( foo const & lhs, foo const & rhs )
{      }
template < typename Lhs >
void
bar( Lhs const & lhs, foo const & rhs )
{      }
template < typename Rhs >
void
bar( foo const & lhs, Rhs const & rhs )
{      }
struct baz : foo
{      };
int
main( void )
{
      bar( baz( ), baz( ) ); // error: ambiguous overload
}
I thought that the compiler would deduce bar( foo const &, foo
const & ).

Why?  The compiler has to choose between three functions:

    void bar( foo const&, foo const& ) ;
    void bar( baz cosnt&, foo const& ) ;
    void bar( foo const&, baz const& ) ;

Note the second and third bar() functins are actually function
template "specializations", so it would be more accurate to present
the three bar() candidate functions as:

void bar( foo const&, foo const& ) ;

template <>
void bar<baz>( baz cosnt&, foo const& ) ;

template <>
void bar said:
For the first argument, the second function is the best match,
and for the second, the last function.  How can the compiler
decide?

Presumably in the same way that the C++ compiler would choose the best
bar() overload - if there were only the first bar() functions from
which to choose. In other words, once the third bar() function is
eliminated, the call to bar() is no longer ambiguous. There is no
ambiguity because - when the compiler has to choose between between:

void bar( foo const&, foo const& ) ;

template <>
void bar<baz>( baz cosnt&, foo const& ) ;

the C++ compiler will - all other things being equal - prefer the non-
template function over the template function.

So what makes this example interesting (and in fact counterintuitive),
is that the C++ compiler finds the call to bar() "ambiguous" - even
though one of the three bar() candidates is considered a better match
(being the only non-template function candidate) than the other two.
Or to put it another way, why is the non-template function a better
match than one other template function candidate - but not a better
match than two other template function candidates?

Greg
 
J

James Kanze

In my opinion the best candidate would be the first function
since baz 'is-a' foo.

But baz is even more a baz. Matching a baz to a baz const& is
(and certainly should be) preferred over matching it to a foo
const&.
At the very worst, it would force the user to define a
specialization to narrow things down further. As it currently
stands, since the language's ranking system cannot resolve the
overload sensibly and so there is no real solution!

There is no sensible resolution. Both of the template
instanciations are clearly and intuitively better matches than
the non-template version, and neither is a better match than the
other.
'bar' is actually an overloaded operator, so that really won't
work in this situation.

Yes. It's a known fact that inheritence and operator
overloading don't work well together.
 
J

James Kanze

Code:
struct foo
{      };
void
bar( foo const & lhs, foo const & rhs )
{      }
template < typename Lhs >
void
bar( Lhs const & lhs, foo const & rhs )
{      }
template < typename Rhs >
void
bar( foo const & lhs, Rhs const & rhs )
{      }
struct baz : foo
{      };
int
main( void )
{
bar( baz( ), baz( ) ); // error: ambiguous overload
}
I thought that the compiler would deduce bar( foo const &, foo
const & ).
Why? The compiler has to choose between three functions:
void bar( foo const&, foo const& ) ;
void bar( baz cosnt&, foo const& ) ;
void bar( foo const&, baz const& ) ;
Note the second and third bar() functins are actually function
template "specializations", so it would be more accurate to present
the three bar() candidate functions as:
void bar( foo const&, foo const& ) ;
template <>
void bar<baz>( baz cosnt&, foo const& ) ;
template <>
void bar<baz>( foo const&, baz const& ) ;

True, but that doesn't change anything. A template
specialization is no longer a template (and of course, only the
specializations play a role in overload resolution).
Presumably in the same way that the C++ compiler would choose
the best bar() overload - if there were only the first bar()
functions from which to choose. In other words, once the third
bar() function is eliminated, the call to bar() is no longer
ambiguous.

No, but if I understand correctly, the resolution isn't the one
the original poster wanted. Basically, he doesn't want the
templates to be considered if the non-template version can be
called, or at least, he wants the template versions to be
ignored if the type derives from bar.
There is no ambiguity because - when the compiler
has to choose between between:
void bar( foo const&, foo const& ) ;
template <>
void bar<baz>( baz cosnt&, foo const& ) ;
the C++ compiler will - all other things being equal - prefer
the non- template function over the template function.

But all other things aren't equal here. In the second version,
the first argument is an exact match, where as in the second,
there is a derived to base conversion. So the second function
is a better match for the first argument, both functions are
equal for the second, and the compiler chooses the second
function.
So what makes this example interesting (and in fact
counterintuitive), is that the C++ compiler finds the call to
bar() "ambiguous" - even though one of the three bar()
candidates is considered a better match (being the only
non-template function candidate) than the other two.

The rule to prefer a non-template function only comes into play
when there are no arguments for which one function is a better
match. That's not the case here.
Or to put it another way, why is the non-template function a
better match than one other template function candidate - but
not a better match than two other template function
candidates?

Because both of the template functions are better matches for at
least one argument. See §13.3.3:

Given these definitions, a viable function F1 is defined
to be a better function than another viable function F2
if for all arguments i, ICSi(F1) is not a worse
conversion sequence than ICSi(F2), and then
-- for some argument j, ICSj(F1) is a better conversion
sequence than ICSj(F2), or, if not that,
-- F1 is a non-template function and F2 is a function
template specialization, or, if not that,
[...]

The first point holds, so we don't even get to the second.
 
S

sebastian

Matching a baz to a baz const& is (and certainly should be) preferred over matching it to a foo const&.

However sound the logic behind the standard's ranking system may be,
it is still *effectively* broken because the ambiguity simply cannot
be removed. How is that preferable?
 
J

James Kanze

However sound the logic behind the standard's ranking system
may be, it is still *effectively* broken because the ambiguity
simply cannot be removed.

Sure it can. Just static_cast the arguments to the exact type.
How is that preferable?

It's preferable that the standard resolve the largest number of
cases in a reasonable, intuitive fashion. And in cases of
doubt, where there is no good intuitive answer, it's better to
get a compiler error than for the compiler to just choose one at
random.
 
S

sebastian

It's preferable that the standard resolve the largest number of cases in a reasonable, intuitive fashion.

Well, it just seems counterintuitive to me. Anyway, thanks for the
response.

Cheers,
- Sebastian  
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top