Однако этот код компилируется в AppleClang 6.0.0.6000056.
Ответ 1
В [namespace.udecl] мы имеем:
Когда декларация использования приносит имена из базового класса в область производного класса, функции-члены и шаблоны функций-членов в переопределении производного класса и/или скрыть функции-члены и функцию-член шаблоны с тем же именем, список параметров (8.3.5), cv-qualification и ref-qualifier (если есть) в базовый класс (а не конфликтующий).
В стандарте явно указано, что введенные имена не будут конфликтовать с именами в базовом классе. Но он ничего не говорит о конфликтующих именах.
В разделе также говорится:
Использование-объявления является декларацией и поэтому может использоваться повторно, где (и только там) несколько объявления разрешены. [Пример:
struct B {
int i;
};
struct X : B {
using B::i;
using B::i; // error: double member declaration
};
-end пример]
И интересно, в следующем примере это GCC, который с радостью компилирует его (и печатает A), в то время как Clang позволяет построить C, но отклоняет вызов foo как двусмысленный:
struct A {
void foo() { std::cout << "A\n"; }
};
struct B {
void foo() { std::cout << "B\n"; }
};
struct C : A, B {
using A::foo;
using B::foo;
};
int main()
{
C{}.foo();
return 0;
}
Итак, короткий ответ - я подозреваю, что это недоказано в стандарте и что оба компилятора делают приемлемые вещи. Я бы просто отказался от написания такого кода для общего здравомыслия.
Ответ 2
Декларация является законной.
Вызов, который является законным и должен работать в любом месте, и его можно вызывать только из класса и производных классов, и он может быть вызван из любого класса. Вы заметите, что это мало смысла.
Нет правил, запрещающих эту конструкцию в объявлениях (импортируя имя дважды из двух разных базовых классов с одной и той же сигнатурой), и даже используется в "реальном" коде, где производный класс отправляется и скрывает имя после того, как они импортируются.
Если вы не скрываете это, вы находитесь в странной ситуации, когда одна и та же функция A::propose
является и защищенной и общедоступной одновременно, так как она дважды называется (юридически) в та же область с различным контролем доступа. Это... необычно.
Если вы находитесь в классе, в подзапросе говорится, что вы можете его использовать:
[class.access.base]/5.1
Член m доступен в точке R, когда он назван в классе N, если - (5.1) m как член из N является общедоступным
и propose
явно публично. (это также protected
, но нам не нужно читать в этом случае!)
В другом месте мы имеем противоречие. Вам говорят, что вы можете использовать его везде без ограничений [class.access]/1 (3). И вам говорят, что вы можете использовать его только в определенных обстоятельствах [class.access]/1 (2).
Я не уверен, как разрешить эту двусмысленность.
Остальная часть логического цикла:
В [namespace.udecl]/10 мы имеем:
Использование-декларация является объявлением и поэтому может использоваться повторно там, где (и только там) разрешено несколько деклараций.
И [namespace.udecl]/13:
Так как декларация использования является объявлением, ограничения на объявления с тем же именем в той же декларативной области
поэтому каждый из этих using X::propose;
является декларациями.
[basic.scope] не имеет применимых ограничений для двух функций с одним и тем же именем в области, отличных от [basic.scope.class]/1 (3), который гласит, что если переупорядочение объявлений изменяет программу, программа плохо сформирован. Поэтому мы не можем сказать, что побеждает более поздний.
Две декларации функций-членов в той же области действия являются законными в соответствии с [basic.scope]. Однако в разделе [over] существуют ограничения на две функции-члены с тем же именем.
[over]/1 состояния:
Если для одного имени в одной и той же области указано два или более разных объявления, это имя считается перегруженным
И есть некоторые ограничения на перегрузку. Это то, что обычно предотвращает
struct foo {
int method();
int method();
};
от юридического. Однако:
[over.load]/1 состояния:
Не все объявления функций могут быть перегружены. Здесь указаны те, которые не могут быть перегружены. Программа плохо сформирована, если она содержит два таких неперегружаемых объявления в одной области. [Примечание: это ограничение применяется к явным объявлениям в области видимости и между такими декларациями и декларациями, сделанными с помощью декларации использования (7.3.3). Он не применяется к наборам функций, сгенерированных в результате поиска имени (например, из-за использования директив) или разрешения перегрузки (например, для функций оператора). -end note
в заметке явно разрешены символы, введенные через два использования-декларации, из-за ограничений перегрузки! Правила применяются только к двум явным объявлениям в пределах области видимости или между явным объявлением внутри области действия и декларацией использования.
На двух объявлениях-объявлениях есть нулевые ограничения. Они могут иметь одно и то же имя, и их подписи могут конфликтовать столько, сколько захотите.
Это полезно, потому что обычно вы можете пойти, а затем скрыть свое объявление (с объявлением в производном классе), и ничего не получается [namespace.udecl]/15:
Когда декларация использования приносит имена из базового класса в область производного класса, функции-члены и шаблоны функций-членов в производном классе переопределяют и/или скрывают функции-члены и шаблоны-члены-члены с тем же именем, список (8.3.5), cv-qualification и ref-qualifier (если есть) в базовом классе (а не в конфликте).
Однако это не делается здесь. Затем мы вызываем метод. Выдается разрешение перегрузки.
См. [namespace.udecl]/16:
В целях разрешения перегрузки функции, вводимые использование-объявления в производный класс будет рассматриваться так, как если бы они были членами производного класса. В частности, неявный этот параметр обрабатывается так, как если бы он был указателем на производный класс, а не на базовый класс. Это не влияет на тип функции, и во всех остальных отношениях функция остается членом базовый класс.
Поэтому мы должны рассматривать их так, как если бы они были членами производного класса с целью разрешения перегрузки. Но здесь есть еще 3 объявления:
protected:
int A::propose(); // X
int A1::propose(int); // Y
public:
int A::propose(); // Z
Таким образом, вызов B1().propose()
учитывает все 3 объявления. Оба X
и Z
равны. Однако они относятся к одной и той же функции, а состояния разрешения перегрузки - двусмысленность, если выбраны две различные функции. Поэтому результат не является двусмысленным. Там могут быть нарушения контроля доступа или нет, в зависимости от того, как вы читаете стандарт.
[over.match]/3
Если наилучшая жизнеспособная функция существует и уникальна, разрешение перегрузки преуспевает и дает результат в результате. В противном случае разрешение перегрузки выходит из строя, а вызов плохо сформирован. Когда разрешение перегрузки преуспевает, и наилучшая жизнеспособная функция недоступна (раздел 11) в том контексте, в котором она используется, программа плохо сформирована.