It is because name lookup stops if it finds a name in one of your bases. It won't look beyond in other bases. The function in B shadows the function in A. You have to re-declare the function of A in the scope of B, so that both functions are visible from within B and C:
class A
{
public:
void foo(string s){};
};
class B : public A
{
public:
int foo(int i){};
using A::foo;
};
class C : public B
{
public:
void bar()
{
string s;
foo(s);
}
};
Edit: The real description the Standard gives is (from 10.2/2):
The following steps define the result of name lookup in a class scope, C. First, every declaration for the
name in the class and in each of its base class sub-objects is considered. A member name f in one sub-
object B hides a member name f in a sub-object A if A is a base class sub-object of B. Any declarations
that are so hidden are eliminated from consideration. Each of these declarations that was introduced by a
using-declaration is considered to be from each sub-object of C that is of the type containing the declara-
tion designated by the using-declaration.96) If the resulting set of declarations are not all from sub-objects
of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is
an ambiguity and the program is ill-formed. Otherwise that set is the result of the lookup.
It has the following to say in another place (just above it):
For an id-expression [something like "foo"], name lookup begins in the class scope of this; for a qualified-id [something like "A::foo", A is a nested-name-specifier], name lookup begins in the scope of the nested-name-specifier. Name lookup takes place before access control (3.4, clause 11).
([...] put by me). Note that means that even if your foo in B is private, the foo in A will still not be found (because access control happens later).