Следующая программа определяет два шаблона функций, A::foo<>()
а также B::foo<>()
в двух отдельных пространствах имен (A
а также B
). Два функциональных шаблона идентичны в подписи, и отличаются только аргументом по умолчанию, назначенным их второму параметру шаблона. В конце концов, их имена попадают в сферу main()
соответствующей парой using
декларации:
#include <type_traits>
namespace A
{
template<
typename T,
typename = typename std::enable_if<
std::is_same<T, int>::value // Let this be condition C
>::type
>
void foo(T) { }
}
namespace B
{
template<
typename T,
typename = typename std::enable_if<
!std::is_same<T, int>::value // This is the negation of C
>::type
>
void foo(T) { }
}
int main() {
using A::foo;
using B::foo; // COMPILES: Is this legal?
foo(42); // Invokes A::foo(), non-ambiguous because of SFINAE
}
Я бы ожидал второго using
здесь возникает сообщение об ошибке компиляции: в конце концов, это то, что я получаю, когда пытаюсь определить эти два шаблона в одном и том же пространстве имен.
К моему удивлению, каждый компилятор, на котором я это пробовал (GCC 4.7.2, GCC 4.8.0 beta, ICC 13.0.1, Clang 3.2) компилирует программу и вызывает A::foo()
,
ВОПРОС № 1: это правильно? Это возможно случай «Диагностика не требуется«Ссылки на стандарт C ++ 11 приветствуются.
Рассмотрим теперь этот вариант вышеуказанной программы, который в основном достигает того же эффекта, используя классы, а не пространства имен:
#include <type_traits>
struct X
{
template<
typename T,
typename = typename std::enable_if<
std::is_same<T, int>::value // Here is condition C again
>::type
>
static void foo(T) { }
};
struct Y
{
template<
typename T,
typename = typename std::enable_if<
!std::is_same<T, int>::value // And the negation of C again
>::type
>
static void foo(T) { }
};
struct Z : X, Y
{
using X::foo;
using Y::foo; // COMPILES: Is this legal?
};
int main() {
Z::foo(42); // Invokes X::foo(), non-ambiguous because of SFINAE
}
Эта программа также компилируется на всех вышеупомянутых компиляторах, в то время как я ожидаю, что ошибка компилятора будет вызвана второй using
декларация.
ВОПРОС № 2: это правильно? Это возможно случай «Диагностика не требуется«Ссылки на стандарт C ++ 11 приветствуются.
По вопросу № 1: кажется, что это разрешено, потому что нет правила, запрещающего это. Стандарт C ++ 11 упоминает об этом в примечании рядом с правилом, запрещающим это, если функция, введенная объявлением using, конфликтует с функцией, которая объявлена непосредственно в пространстве имен, в которое объявление using вводит имя.
Это говорит в §7.3.3 [namespace.udecl] / 14:
Если объявление функции в области пространства имен или блока имеет
то же имя и те же типы параметров, что и функция, представленная
использование-объявление, и объявления не объявляют то же самое
функция, программа плохо сформирована. […]
Это нормативный текст, который определяет определенный тип конфликта как недействительный.
Нет нормативного текста о том, что два использующих объявления не конфликтуют одинаково, но обратите внимание, что аналогичный конфликт между двумя использующими объявлениями недопустим в точке объявления. Тот же абзац продолжается:
[…] [Примечание: два объявления-использования могут вводить функции с
то же имя и те же типы параметров. Если для вызова неквалифицированного
имя функции, разрешение перегрузки функции выбирает функции
введенный такими объявлениями об использовании, вызов функции
плохо сформирован.
[ Пример:namespace B { void f(int); void f(double); } namespace C { void f(int); void f(double); void f(char); } void h() { using B::f; // B::f(int) and B::f(double) using C::f; // C::f(int), C::f(double), and C::f(char) f(’h’); // calls C::f(char) f(1); // error: ambiguous: B::f(int) or C::f(int)? void f(int); // f(int) conflicts with C::f(int) and B::f(int) }
— конец примера] — конец заметки]
Для вопроса № 2 аналогичный нормативный текст, который описывает случай конфликта между объявлением члена класса и объявлением использования уровня класса, находится в следующем абзаце, §7.3.3 / 15:
Когда объявление использования переносит имена из базового класса в производный
область действия класса, функции-члены и шаблоны функций-членов в
производный класс переопределяет и / или скрывает функции-члены
шаблоны функций с тем же именем, параметр-тип-список (8.3.5),
cv-квалификация и ref-квалификатор (если есть) в базовом классе (скорее
чем противоречивый). [Примечание: для объявлений об использовании, которые называют
конструктор, см. 12.9. — конец примечания]
Опять же, нет текста о конфликтах между использованием объявлений, но аналогично примечанию в предыдущем случае, наличие двух объявлений использования, которые потенциально обозначают конфликтующие функции, не является неправильным, потому что нет текста, который запрещает сосуществование этих объявлений. И как в предыдущем случае: если для вызова функции разрешение перегрузки выбирает обе эти функции, то вызов неверен.
В вашем примере SFINAE всегда удаляет одну из потенциально конфликтующих функций из набора перегрузки, поэтому ни в одном из случаев проблем не возникает.
Других решений пока нет …