Ответ 1
GCC прав.
Давайте начнем с немного более простого примера, а затем докажем, что исходный пример следует за одним и тем же шаблоном:
template<typename T>
void bar(T const&)
{
// Shall not fire
static_assert(std::is_same<T, int>::value, "Error!");
}
int main()
{
int x = 0;
bar(x); // 1 - Assertion won't fire
int const y = 0;
bar(y); // 2 - Assertion won't fire
}
Что здесь происходит? Прежде всего, согласно § 14.8.2.1/3:
[...] Если P является ссылочным типом, тип, обозначаемый P, используется для вывода типа. [...]
Это означает, что вычет типа попытается сопоставить T const
с int
(в случае 1) и с int const
(в случае 2). Во втором случае подстановка int
для T
даст идеальное соответствие, так что легко; в первом случае у нас есть const
, чтобы добиться идеального соответствия. Но именно здесь вступает в игру статья 14.8.2.1/4:
[...] Если исходный P является ссылочным типом, выведенный A (то есть тип, на который ссылается ссылка) может быть больше cv-квалифицированных, чем преобразованный A. [...]
Здесь замена int
на T
дает нам выведенный int const
, который более cv-квалифицирован, чем int
(тип аргумента x
). Но это приемлемо из-за § 14.8.2.1/4 выше, поэтому даже в этом случае T
выводится int
.
Теперь давайте рассмотрим ваш оригинальный пример (слегка скорректированный, но в конечном итоге мы получим исходную версию):
template<typename T>
void bar(T const&)
{
// Does not fire in GCC, fires in VC11. Who right?
static_assert(std::is_same<T, char[4]>::value, "Error!");
}
int main()
{
char x[] = "foo";
bar(x);
char const y[] = "foo";
bar(y);
}
Помимо того, что я заменил int
на char []
, это пример, и мой первый пример идентичен по своей структуре. Чтобы понять, почему эта эквивалентность имеет место, рассмотрим приведенное ниже утверждение (которое не срабатывает ни в каком компиляторе, как ожидалось):
// Does not fire
static_assert(
std::is_same<
std::add_const<char [4]>::type,
char const[4]
>::value, "Error");
Стандарт С++ 11 предусматривает такое поведение в пункте 3.9.3/2:
Любые cv-квалификаторы, применяемые к типу массива, влияют на тип элемента массива, а не тип массива (8.3.4).
В пункте 8.3.4/1 также указывается:
[...] Любой тип формы "cv-qualifier-seq array of N T" настраивается на "array" из N cv-qualifier-seq T ", а также для" массива неизвестной границы T ". Необязательный атрибут-спецификатор-seq appertains к массиву. [Пример:
typedef int A[5], AA[2][3];
typedef const A CA; // type is "array of 5 const int"
typedef const AA CAA; // type is "array of 2 array of 3 const int"
-end example] [Примечание: "массив из N cv-qualifier-seq T" имеет тип cv-qualify; см. 3.9.3. -end note]
Поскольку теперь ясно, что два примера демонстрируют один и тот же шаблон, имеет смысл применять ту же логику. И это приведет нас по тому же пути рассуждений.
При выполнении вывода типа T const
сопоставляется с char[4]
в первом случае и против char const[4]
во втором случае.
Во втором случае T = char[4]
дает идеальное соответствие, потому что T const
становится char const[4]
после подстановки. В первом случае выведенный A
снова больше cv-qual, чем исходный A
, поскольку подстановка char[4]
для T
дает char const[4]
. Но опять же, это разрешено 14.8.2.1/4, поэтому T
следует выводить как char[4]
.
Наконец, вернемся к вашему первоначальному примеру. Поскольку строковый литерал "str"
также имеет тип char const[4]
, T
должен быть выведен как char [4]
, что означает, что GCC прав:
template<typename T>
void foo(T const&)
{
// Shall not fire
static_assert(std::is_same<T, char[4]>::value, "Error!");
}
int main()
{
foo("str"); // Shall not trigger the assertion
}