Abstract/non-abstract overload/inheritance

Hi all,

I was stuck on this for a few hours today, until somebody suggested me a solution that turned out to work, surprisingly.. My problem is that neither him or me understand why, so I'm turning to you :)

For those who prefer verbose explanations (otherwise, jump to the code example), my problem is the following: I define an abstract template class which declares three overloads of a function, defines two of them, and leaves the last one as pure virtual. I then define a derived class implementing the virtual method.
The problem is that apparently (and even though the three overloads have different signatures), the derived class need to include a "using" statement to make the overloads defined in the parent class visible. Why?

Here is a minimal example that compiles on VS2010 and g++ 4.5.3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <vector>

#define TEST_TYPE float

using namespace std;

typedef vector<unsigned> vecu_t;

/*
 *  Abstract template class with binary and abstract unary operator().
 */
template <class T>
class A 
{
public:

	A() : bin_ret( static_cast<T>(0) ), const_bin_ret( static_cast<T>(1) ) {}

	T& operator() ( unsigned i, unsigned j )
	{
		cout << "Abstract class, not const." << endl;
		return bin_ret;
	}
	const T& operator() ( unsigned i, unsigned j ) const
	{
		cout << "Abstract class, const." << endl;
		return const_bin_ret;
	}

	virtual const T& operator() ( vecu_t v ) const = 0;

protected:
	T bin_ret;
	T const_bin_ret;
};

/*
 *  Derived template class implementing unary operator().
 */
template <class T>
class B : public A<T>
{
public:

	using A<T>::operator(); // Without that, it does not compile... why?

	B() : A<T>(), un_ret( static_cast<T>(2) ) {}

	const T& operator() ( vecu_t v ) const
	{
		cout << "Derived class." << endl;
		return un_ret;
	}

private:
	T un_ret;
};



int main()
{
	// Instance of derived class
	B<TEST_TYPE> b = B<TEST_TYPE>(); 

	// 0 vector 1x3
	vecu_t v(3,0u);

	// Try methods
	const TEST_TYPE u_cf = b( v );
	const TEST_TYPE b_cf = b( 1u,2u ); // expected const method to be called here
	TEST_TYPE b_f = b( 1u,2u );
}


There is another smaller question; the first call to b(1u,2u) should return a const float, but instead, it seems that the non-const method is called twice; why?
const TEST_TYPE b_cf = b( 1u,2u ); // expected const method to be called here

b it'self is not const, nor is it in the context in which it is being used, so it calls the operator()(x,y) non const - return type does not determine the constness of the call.

This is true for const/volatile CV Qualifyiers

The reason you have to use the using is because of the way lookup and overload resolution works. Without the using, which is totally feasible (but ugly) it would compile like this.

1
2
3
const TEST_TYPE b_cf = b.A<TEST_TYPE>::operator()( 1u,2u ); // expected const method to be called here
	
	TEST_TYPE b_f = b.A<TEST_TYPE>::operator()(1u,2u);


The fact that the operator() exists in the derived class stops lookup through the hierarchy. Then it tries overload resolution, there are incompatabilites with the arguments so it fails to find it, and because a presence of the operator was found in the derived, lookup has stopped traversing up the hierarchy, thus hiding the operators.
Last edited on
Whether a const or non-constant member function is called depends on whether the object it is being called on is constant or not (it does NOT depend on the return type)

a const function can be called on a const or a non-const object (but given a choice of both function types the compiler will prefer to call the non-const function on a non-const object)

BUT only a const function can be called on a const object.


In your code:
1
2
	// Instance of derived class
	B<TEST_TYPE> b = B<TEST_TYPE>(); 


the b object is non-const - so given a choice of calling a const or non-const member function - the compiler will give preference to the non-const one.

If you change the b oject to be const - const B<TEST_TYPE> b = B<TEST_TYPE>(); - you will see the compiler change the function call.
Thank you clanmjc and guestgulkan for your answers, I now understand why the const method was not called.

Small problem solved :) now the big one...
Last edited on
See my edit.
Thanks again clanmjc, I think this is very close to what I don't understand.

Could you please refer me to a resource (reference page or book chapter) where I could find a more formal and detailed explanation of how this "lookup and overload resolution" processes you talk about are working?

Put simply, what I don't understand is why there would be an ambiguity in this situation (I personally see none)?
Last edited on
Here is part of it, straight from the standard...

3.4 Name lookup [basic.lookup]
1 The name lookup rules apply uniformly to all names (including typedef-names (7.1.3), namespace-names (7.3),
and class-names (9.1)) wherever the grammar allows such names in the context discussed by a particular
rule. Name lookup associates the use of a name with a declaration (3.1) of that name. Name lookup shall
find an unambiguous declaration for the name (see 10.2). Name lookup may associate more than one declaration with a name if it finds the name to be a function name; the declarations are said to form a set
of overloaded functions (13.1). Overload resolution (13.3) takes place after name lookup has succeeded.
The access rules (Clause 11) are considered only once name lookup and function overload resolution (if
applicable) have succeeded. Only after name lookup, function overload resolution (if applicable) and access
checking have succeeded are the attributes introduced by the name’s declaration used further in expression
processing (Clause 5).
2 A name “looked up in the context of an expression” is looked up as an unqualified name in the scope where
the expression is found.
3 The injected-class-name of a class (Clause 9) is also considered to be a member of that class for the purposes
of name hiding and lookup.


Also from standard.
3.3.10 Name hiding [basic.scope.hiding]
1 A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived
class (10.2).
2 A class name (9.1) or enumeration name (7.2) can be hidden by the name of a variable, data member,
function, or enumerator declared in the same scope. If a class or enumeration name and a variable, data
member, function, or enumerator are declared in the same scope (in any order) with the same name, the
class or enumeration name is hidden wherever the variable, data member, function, or enumerator name is
visible.
3 In a member function definition, the declaration of a name at block scope hides the declaration of a member
of the class with the same name; see 3.3.7. The declaration of a member in a derived class (Clause 10) hides
the declaration of a member of a base class of the same name; see 10.2.


Link to related post
http://stackoverflow.com/questions/6727087/c-virtual-function-being-hidden

Link to the standard(draft)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf
Last edited on
Thanks a lot clanjmc, I'll get deeper in these to understand what was wrong.
You've been very helpful! Problem solved.
Topic archived. No new replies allowed.