Я попытаюсь показать, каким образом clang интерпретирует черновик иначе, чем g cc и msv c. Когда вы вызываете F(q)
внутри определения void A::C::Test()
, компилятор пытается найти имя F
в областях, в которых выполняется этот вызов, и прекращает поиск больших областей, как только это имя (имя Особых перегрузок не обнаружено. Тело void A::C::Test()
находится внутри пространства имен C
, которое находится внутри A
, которое находится внутри глобального пространства имен. Поэтому, когда вы пишете директиву using в глобальной области видимости, соответствующий отрывок в стандарте [basi c .lookup.unqual]:
- Во всех случаях, перечисленных в [basi c .lookup.unqual], области поиска объявлений в порядке, указанном в каждой из соответствующих категорий; поиск имени заканчивается, как только для имени найдено объявление. Если декларация не найдена, программа некорректна.
- Объявления из пространства имен, назначенного директивой using, становятся видимыми в пространстве имен, включающем директиву using; см. [namespace.udir]. Для целей правил поиска без оговорок, описанных в [basi c .lookup.unqual], объявления из пространства имен, назначенного директивой using, считаются членами этого вмещающего пространства имен.
по пункту 2 подразумевает, что имя F
(от A::B::F
) отображается так, как если бы оно было объявлено в глобальной области видимости, но при поиске имени по пункту 1 имя F
быстрее найдется в пространстве имен A
и дальше смотреть не буду. Таким образом, он находит только void F(V<T> v)
, и это объявление не подходит для вызова.
Более интересная история с директивой using, помещающей внутри тела функции. Разница в подходе заключается в том, появляется ли определение void A::C::Test()
(которое также является объявлением) в глобальном пространстве имен или в пространстве имен A::C
. Если вы придерживаетесь мнения, что это глобальное пространство имен (как, очевидно, и делает clang), то пространство имен, содержащее директиву using, написанную внутри функции, является глобальным пространством имен, и мы возвращаемся к объяснению выше. Но если вы верите, что оно появляется в пространстве имен A::C
, то вы вносите (среди прочего) объявление void F(Q<T>)
в A::C
и регулярный поиск находит его.
Наблюдение за бонусом - это то, почему вы все равно сможете звонить F(V<double>{})
, поскольку теперь объявление void F(Q<T>)
находится в более узкой области видимости, а ответом является поиск, зависящий от аргумента, пространство имен, где V<T>
- это Заявление будет объявлено тоже.
Это история, которая пугает ученика, но если вам нужно практическое руководство, не используйте ярлыки с пространствами имен, используйте
namespace A::C
{
void Test()
{
...
}
}
, чтобы пространство имен, в котором оно объявлено, не подлежит обсуждению и помещается с использованием пространства имен A :: B; либо в области имен или в области функций. (И я постараюсь выяснить, какова была реальная цель определения декларации void A::C::Test()
с более мудрыми людьми).