Это потому, что поиск имени останавливается, если он находит имя в одной из ваших баз. В других базах дальше не пойдет. Функция в B затеняет функцию в A. Вы должны повторно объявить функцию A в области B, чтобы обе функции были видны из B и 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);
}
};
Изменить: настоящее описание, которое дает Стандарт (с 10.2 / 2):
Следующие шаги определяют результат поиска имени в области класса C. Сначала рассматривается каждое объявление имени в классе и в каждом из его подобъектов базового класса. Имя члена f в одном подобъекте B скрывает имя члена f в подобъекте A, если A является подобъектом базового класса B. Любые объявления, которые так скрыты, исключаются из рассмотрения. Каждое из этих объявлений, которые были введены с помощью объявления-использования, считается исходящим от каждого подобъекта C, который относится к типу, содержащему объявление, указанное в объявлении-использовании. 96) Если результирующий набор объявлений не является все из подобъектов одного типа или набор имеет нестатический член и включает элементы из разных подобъектов, возникает двусмысленность и программа плохо сформирована. В противном случае этот набор является результатом поиска.
В другом месте (чуть выше) он должен сказать следующее:
Для id-выражения [ что-то вроде "foo" ] поиск имени начинается в области класса this; для квалифицированного идентификатора [ что-то вроде «A :: foo», A - это вложенный-указатель-имени ] поиск имени начинается в области видимости вложенного-указателя-имени. Поиск имени выполняется до управления доступом (3.4, раздел 11).
([...] поставлено мной). Обратите внимание, это означает, что даже если ваш foo в B является частным, foo в A все равно не будет найден (потому что управление доступом происходит позже).