I'm going to list both solutions presented in other answers, and detail how they differ.
struct Child : Base
{
int foo(int x) { return Base::foo(x); }
int foo(int, int, int) { std::cout << 3; return 33; }
};
this does exactly what you want, but you have to repeat the signature.
A slightly different result is:
struct Child : Base
{
using Base::foo;
int foo(int,int)=delete;
int foo(int, int, int) { std::cout << 3; return 314; }
};
To see how this is different, imagine we did this:
struct Grandkid : Child
{
using Child::foo;
int foo(double, double) { std::cout << 2; return 42; }
};
and we did:
Grandkid x;
x.foo(3,4);
In the case with =delete, this would generate a compiler error.
3,4 prefers int,int over double,double. When we =delete the int,int overload, it is still considered, selected, and we pick the =deleted one. When we instead exclude it from overload resolution, 3,4 picks double,double.
Manual forwarding excludes int,int overload from being considered. =delete does not.
This is most similar to the imaginary syntax of using Base::foo(int); (ie, only bringing in one of the parent overloads of foo).
Note that Grandkid is just one simple way to detect the difference. The important thing is there is a difference between removing something from overload resolution, and =deleteing the overload.
Live example where it works, and where it doesn't.