Строковый литерал в шаблонах - различное поведение компиляторов

Предположим, что у нас есть следующий код:

template <typename T>
void foo(const T&);

int main()
{
   foo("str");
}

Демонстрация

gcc 4.7.2, clang 3.2, icc 13.0.1

undefined ссылка на `void foo < char [4] > (char const (&) [4]) '

MSVC-11.0

неразрешенный внешний символ "void __cdecl foo < char const [4] > (charconst (&) [4])" (?? $foo @$$ BY03 $$ CBD @@YAXAAY03 $$ CBD @Z)

Обратите внимание на char[4] на первом выходе и char const[4] на втором выходе.

Почему? И кто прав? Можете ли вы привести этот стандарт, пожалуйста?

Ответ 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
}

Ответ 2

GCC правильный; что const в списке аргументов шаблона VS не должно быть:

[C++11: 14.8.2/3]: После этой подстановки выполняются настройки параметров параметров параметров, описанные в 8.3.5. [Пример: тип параметра "void ()(const int, int[5])" становится "void(*)(int,int*)". -end example] [Примечание: квалификатор верхнего уровня в объявлении параметра функции не влияет на функцию но по-прежнему влияет на тип переменной параметра функции внутри функции. -end note] [Пример:

template <class T> void f(T t);
template <class X> void g(const X x);
template <class Z> void h(Z, Z*);

int main() {
  // #1: function type is f(int), t is non const
  f<int>(1);

  // #2: function type is f(int), t is const
  f<const int>(1);

  // #3: function type is g(int), x is const
  g<int>(1);

  // #4: function type is g(int), x is const
  g<const int>(1);

  // #5: function type is h(int, const int*)
  h<const int>(1,0);
}

-end пример]

(пример 4 является подходящим.)

[C++11: 14.8.2/5]: Полученный замененный и скорректированный тип функции используется как тип шаблона функции для вывода аргумента шаблона. [..]

Также возможно релевантно:

Вывод аргументов шаблона из вызова функции
[C++11: 14.8.2.1/2]: Если P не является ссылочным типом:

  • Если A является типом массива, тип указателя, созданный стандартным преобразованием от массива к указателю (4.2), используется вместо A для вывода типа; в противном случае,
  • Если A - тип функции, тип указателя, созданный стандартным преобразованием функции-to-pointer (4.3), используется вместо A для вывода типа; в противном случае,
  • Если A - это тип с квалификацией cv, верхние уровни cv-квалификаторы типа A s игнорируются для вывода типа