Consider a class b with two overloaded methods of foo:
struct b {
void foo(float) {}
void foo(const char *) {}
};
If I derive d privately from b, I can use using to expose b's foo:
struct d : private b {
using b::foo;
};
However, this exposes all overloads. Is there a way to expose only one of them (say, the float one)? For example, in the following, I'd like the last line to fail compilation:
d t;
t.foo(3.13); // d should have this overload
t.foo("hello"); // d shouldn't have this overload
I tried various ways of writing
using b::<i mean only void foo(float), dammit!>
but couldn't get any of them to compile.
Also, obviously it's possible to define in d just the required overload calling b's overload
struct d : private b {
void foo(float f) { b::foo(f); }
};
but the question is if it's possible to do this tersely with using only.
No, that is not possible. A using declaration, just like any other declaration, operates on names.
using b::foo; introduces the name foo into the declaration's containing scope, such that it refers to whatever b::foo refers to. The name b::foo refers to a "family" of overloaded functions, so after the using-declaration, the name foo refers to the same.
If you want to "publish" only some overloads, you have to do it using the trampoline functions you've shown:
struct d : private b {
void foo(float f) { b::foo(f); }
};
As mentioned by #Angew in his answer, an using declaration introduces names in a namespace.
Because of that, you can't pick only your preferred ones up, but you can still do the opposite and = delete the ones you don't want to expose:
struct B {
void f() { }
void f(int) { }
void f(int, char) { }
};
struct D: B {
using B::f;
void f(int) = delete;
};
int main() {
D d;
d.f();
d.f(0, 'c');
// this won't work
// d.f(0);
}
This is not exactly what you were looking for, but it is a workaround to obtain almost the same result.
It follows a comment made by #Yakk that is worth quoting in the answer:
Note that a deleted overload is not the same as not having one. If missing a different overload may be selected, while if deleted it may be instead selected and generate an error.
This is right, if the solution above works for the OP mostly depends on the real problem.
I cannot say that, but still this is a viable solution for some cases
Related
Forgive the obscure title. It's likely a duplicate but I couldn't find the correct phrase.
Consider the following inheritance hierarchy.
class A
{
public:
virtual void Foo(int i) { printf("A::Foo(int i)\n"); }
void Foo(int a, int b) { Foo(a + b); }
};
class B : public A
{
public:
void Foo(int i) override { printf("B::Foo(int i)\n"); }
};
class C : public B
{
public:
void Bar() { Foo(1, 2); } //error C2660: function does not take two arguments
};
A has two methods named Foo with a different number of parameters. Only one of them is virtual.
B overrides the virtual method.
C tries to call the non-virtual method and encounters an error.
Calling the method as A::Foo(1, 2) does work fine.
Question:
Why can't the compiler deduce that the correct method is found on A?
It seems odd we would have to explicitly add A:: in a call such as:
C c;
c.A::Foo(1, 2);
Because the member function named Foo is found at the class scope of B, and then name lookup stops, so the Foo in class A is not visible, and won't be considered for overload resolution, even if the version in class A is more appropriate. It is name hiding.
You can use using to introduce them into the same scope, and make overload resolution work as you expect. Such as:
class C : public B
{
using A::Foo;
using B::Foo;
public:
void Bar() { Foo(1, 2); }
};
See Unqualified name lookup
LIVE
Please consider the following code:
struct A
{
void f()
{
}
};
struct B1 : A
{
};
struct B2 : A
{
};
struct C : B1, B2
{
void f() // works
{
B1::f();
}
//using B1::f; // does not work
//using B1::A::f; // does not work as well
};
int main()
{
C c;
c.f();
return 0;
}
I kindly ask you not to copy paste a standard reply on how to solve the diamond problem ("use virtual inheritance"). What I am asking here is why doesn't a using-declaration work in this case. The exact compiler error is:
In function 'int main()':
prog.cpp:31:6: error: 'A' is an ambiguous base of 'C'
c.f();
I got the impression a using-declaration should work from this example:
struct A
{
void f()
{
}
};
struct B
{
void f()
{
}
};
struct C : A, B
{
using A::f;
};
int main()
{
C c;
c.f(); // will call A::f
return 0;
}
Someone else can find the standard quote but I'm going to explain conceptually.
It doesn't work because a using-declaration only affects name lookup.
Your using-declaration causes name lookup to succeed where it would otherwise fail, that is, it tells the compiler where to find the function f. But it does not tell it which A subobject f acts on, that is, which one will be passed as the implicit this parameter when f is called.
There is only a single function A::f even though there are two A subobjects of C, and it takes an implicit this argument of type A*. In order to call it on a C object, C* must be implicitly converted to A*. This is always ambiguous, and is not affected by any using-declarations.
(This makes more sense if you put data members inside A. Then C would have two of each such data member. When f is called, if it accesses data members, does it access the ones in the A subobject inherited from B1, or the ones in the A subobject inherited from B2?)
There's a note in [namespace.udecl]/p17 that addresses this situation directly:
[ Note: Because a using-declaration designates a base class member
(and not a member subobject or a member function of a base class
subobject), a using-declaration cannot be used to resolve inherited
member ambiguities. For example,
struct A { int x(); };
struct B : A { };
struct C : A {
using A::x;
int x(int);
};
struct D : B, C {
using C::x;
int x(double);
};
int f(D* d) {
return d->x(); // ambiguous: B::x or C::x
}
—end note ]
In addition to T.C.'s answer, I'd like to add that the name lookup in derived class is explained in the standard pretty much in detail in section 10.2.
Here what is said about processing of using-declarations :
10.2/3: The lookup set (...) consists of two component sets: the declaration set, a set of members named f; and the subobject set, a set of subobjects where declarations of these members (possibly including
using-declarations) were found. In the declaration set, using-declarations are replaced by the members they designate, and type declarations (including
injected-class-names) are replaced by the types they designate.
So when you try to declare in struct C
using B1::f; // you hope to make clear that B1::f is to be used
according to the lookup rules, your compiler nevertheless finds the possible candidates: B1::f and B2::f so that it's still ambiguous.
Please consider these types:
struct X
{
static std::string fullyQualifiedName();
};
struct A
{
template <class T> void foo()
{
return foo(T::fullyQualifiedName());
}
protected:
virtual void foo(const std::string& name) = 0;
};
struct B : public A
{
protected:
void foo(const std::string& name);
};
I have a pointer to an instance of B, I'm trying to invoke the templated version of foo like so:
b->foo< X >();
The compiler is complaining: 'X' : illegal use of this type as an expression.
On the other hand, this code's perfectly ok:
A* a = b;
a->foo< X >();
Hence my question.
The problem you are facing is called hiding. Basically the lookup rules in the language will start in the most derived type and work their way back up until it finds the appropriate symbol. In your case it will stop while looking at the B class as it finds void B::foo(const std::string& name). At that level, the only potential overload that it will consider is the one it is seeing.
To avoid hiding you can bring all the other overloads you can add a using declaration:
struct B : A{
using A::foo;
void foo( const std::string & name );
};
The difference is that lookup will work up the hierarchy until it finds the first overload, which is again at B level, but because of the using directive, it will also consider any overload available in A.
Alternatively, you can leave the symbol hidden and force dispatch to the exact class by qualifying the call (note that this has the side effect of disabling dynamic dispatch, which is not a problem here, but could be if the overload to use is virtual):
b->A::foo<X>();
What's the rationale behind the hiding rule in C++?
class A { void f(int); }
class B : public A { void f(double); } // B::f(int) is hidden
If it is a meaningful feature I think it should also be possible to hide functions without defining new functions with the same name: something like this:
class B : public A { hide void f(double); }
but this is not possible.
I don't think it simplifies compilers job, since compilers must anyway be able to unhide functions when you explicitly use the using directive:
class B : public A { using A::f; void f(double); } // B::f(int) NOT hidden
So, how come there is a hiding rule?
It's an hairy question, but apparently the idea is that this hiding feature helps avoiding subtle bugs when making changes to a base class (that could otherwise "steal" calls that before would have been handled by the derived class). Still a change in a base class can influence the result of compilation of derived classes so I don't think I understand 100% this explanation.
I agree that this topic is so frequently discussed that probably the hiding actually increases the amount of "surprises" in C++ programmers.
A detailed discussion about this issue can be found here...
i don't know the original rationale, but since hide or not hide are about equally bad choices wrt. to functions, i'm guessing the rationale is to have uniform rules: the same as for names defined in nested curly-braces scopes.
the hiding helps you in some ways.
adding a method to a base class will by default not affect overload resolution for a derived class.
and you do not run afoul of overload resolution by some mishap directing your call with say argument false, to a base class method with formal argument void*. such things.
cheers & hth.,
I'm sure I've seen this case offered by a C++ bigwig, not sure which:
struct Base {
void f(const Base&);
};
struct Derived : Base {
using Base::f;
void f(double);
};
int main() {
Derived d;
d.f('a'); // calls Derived::f
}
Now, add void f(int); to Base, and the meaning of main changes - it calls Base::f because int is a better match for char - it's an integer promotion rather than a standard conversion.
It's not clear whether that change to the base would really be intended by the programmer to catch calls with char, so requiring using to be explicit means the default behavior is that the change doesn't affect the calling code. I believe it's a marginal call, but I think the committee decided that base classes in C++ were fragile enough as they are, without this too :-)
There's no need for a "hide" keyword because there's no comparable case for hiding "f" from the Base when it isn't overloaded in Derived.
Btw, I've chosen the types, and char is deliberately incongruous. You can get more subtle cases with int vs unsigned int rather than int vs char.
Another reason for hiding base class's member function (with same name but different signatures) might be due to ambiguity caused by optional parameters. Consider the following example:
#include <stdio.h>
class A
{
public:
int foo(int a, int b=0)
{
printf("in A : %d, %d\n", a, b);
}
};
class B : public A
{
public:
int foo(int a)
{
printf("in B : %d\n", a);
foo(a); //B:foo(a) will be called unless we explicitly call A:foo(a)
foo(a, 1); // compile error: no matching function for call to B:foo(int&, int)
}
};
int main()
{
B b;
b.foo(10);
return 0;
}
If the foo method in base class hadn't become hidden, it wouldn't be possible for compiler to decide whether A::foo should be called or B::foo since the following line matches both signatures:
foo(a);
Probably, the reason is template specialization. I give you an example:
template <int D> struct A { void f() };
template <> struct A<1> { void f(int) };
template <int D>
struct B: A<D>
{
void g() { this->f(); }
};
The template class B has a method f(), but until you don't create an instance of the class B you don't know the signature. So the call this->f() is anytime "legal". Both GCC and CLang don't report error until you create the instance. But when you call the method g() on a B<1> instance they indicate the error. So the hiding rule keep simpler to check if your code is valid.
I report the last part of code used in my example.
int main (int argc, char const *argv[])
{
B<0> b0; /* valid */
B<1> b1; /* valid */
b0.g(); /* valid */
b1.g(); /* error: no matching function for call to ‘B<1>::f()’ */
return 0;
}
If I have a code like this:
struct A {
virtual void f(int) {}
virtual void f(void*) {}
};
struct B : public A {
void f(int) {}
};
struct C : public B {
void f(void*) {}
};
int main() {
C c;
c.f(1);
return 0;
}
I get an error that says that I am trying to do an invalid conversion from int to void*. Why can't compiler figure out that he has to call B::f, since both functions are declared as virtual?
After reading jalf's answer I went and reduced it even further. This one does not work as well. Not very intuitive.
struct A {
virtual void f(int) {}
};
struct B : public A {
void f(void*) {}
};
int main() {
B b;
b.f(1);
return 0;
}
The short answer is "because that's how overload resolution works in C++".
The compiler searches for functions F inside the C class, and if it finds any, it stops the search, and tries to pick a candidate among those. It only looks inside base classes if no matching functions were found in the derived class.
However, you can explicitly introduce the base class functions into the derived class' namespace:
struct C : public B {
void f(void*) {}
using B::f; // Add B's f function to C's namespace, allowing it to participate in overload resolution
};
Or you could do this:
void main()
{
A *a = new C();
a->f(1); //This will call f(int) from B(Polymorphism)
}
Well I think first of all you did not understand what virtual mechanism or polymorhism. When the polymorphism is achieved only by using object pointers. I think you are new to c++. Without using object pointers then there is no meaning of polymorphism or virtual keyword use base class pointer and assign the desired derived class objects to it. Then call and try it.