Несогласованное разрешение перегрузки для функций-членов constexpr в разных компиляторах

#c

#c

Вопрос:

Я столкнулся с ошибкой компилятора, которая воспроизводится только в gcc, я сузил ее до минимально воспроизводимого образца, который также терпит неудачу в msvc, но все еще отлично компилируется с clang. Вот код:

 struct vec
 { 
 float _x[2];

 оператор constexpr с плавающей точкой[](int index) const { возвращает _x[индекс]; }
 floatamp; operator[](int index) { возвращает _x[индекс]; }
 };

 структурный мат
 {
 vec _x[2];

 оператор constexpr vec[](int index) const { возвращает _x[индекс]; }
 vecamp; operator[](int index) { возвращает _x[индекс]; }
 };

 строка с плавающей точкой constexpr (float f)
 { 
 возвращает f;
 }

 constexpr с плавающей точкой (mat const amp; m)
 { 
 возвращает m[0][0]; // сбой в gcc 5  , msvc
 }

 constexpr с плавающей точкой два (mat const amp; m)
 { 
 строка возврата (m[0][0]); // сбой в gcc 5  
 }

Из того, что я могу сказать, разрешение перегрузки для vec::operator [] в строке 24 не учитывает перегрузку const (строка 5), потому что mat::operator[] const (строка 13) возвращает по значению, а не по ссылке const, но я не уверен, почему это мешает рассмотрению vec::operator [] const . Сообщение об ошибке от gcc:

 : В функции 'constexpr с плавающей точкой один (const matamp;)':
 :24:18: ошибка: вызов функции, отличной от constexpr 'floatamp; vec::operator[](int)'
 возвращает m[0][0]; // сбой в gcc 5 , msvc

И из msvc:

 (22): ошибка C3615: функция constexpr 'one' не может привести к постоянному выражению
 (24): примечание: сбой был вызван вызовом неопределенной функции или одной из не объявленных 'constexpr'
 (24): примечание: смотрите использование 'vec::operator []'

Исходный код отлично компилируется в msvc, но пример этого не делает, поэтому мне потребовалось немного времени, чтобы найти то, что позволяло ему работать с msvc. По-видимому, передача возвращаемого значения через другую функцию constexpr каким-то образом заставляет msvc учитывать перегрузку const, но я понятия не имею, в чем причина этого. Это ошибка или результат каких-то эзотерических языковых правил? Какой компилятор правильный?

Последний вопрос здесь заключается в том, что это проблема только потому, что const перегружает возвращаемое значение по значению, если они возвращаются по ссылке const, ни в одном компиляторе нет ошибок. Является ли возврат по значению здесь бесполезной пессимизацией, которую я должен удалить?

Ответ №1:

Какой компилятор правильный?

В этом случае оба компилятора корректны (или, по крайней мере, не некорректны). Стандарт C гласит ([dcl.constexpr]/5), что любая функция constexpr, которая не может быть вызвана в постоянном выражении, делает программу «неправильно сформированной; диагностика не требуется». Это означает, что программа с этим условием неверна, но реализации (компиляторы) не обязаны печатать какое-либо диагностическое сообщение об этом.

Вы правильно поняли, что выражение m[0][0] сначала вызывает constexpr vec mat::operator[](int) const; , а затем вызывает floatamp; vec::operator[](int); , и проблема в том, что это vec::operator[] не функция constexpr.

но я не уверен, почему это мешает рассмотрению vec::operator[] const .

Обратите внимание, что разрешение перегрузки для любого вызова функции, включая перегружаемый оператор, использует только типы и категории значений подвыражений, включая неявный аргумент типа класса в случае нестатической функции-члена. В частности, здесь не учитывается, используется ли выражение в постоянном выражении или в постоянно выполняемой части функции constexpr. Было бы слишком запутанно, если бы два выражения, написанные с помощью одного и того же кода, вызывали совершенно разные функции в зависимости от того, как это выражение используется.

В этом случае m[0][0] полностью допустимо, когда не используется в постоянном выражении:

 float runtime_func(const matamp; m) { return m[0][0]; }
  

и при таком использовании он вызывает, constexpr vec mat::operator[](int) const; поскольку тип m имеет значение const, а затем, floatamp; vec::operator[](int); поскольку тип m[0] является vec , не имеет значения const. So one и two вызывают одни и те же функции, и обе они неверно сформированы, диагностика не требуется.

Я не могу сказать, почему MSVC выдает ошибку для функции one , но не для function two . Но я не думаю, что это на самом деле используется float vec::operator[](int) const; вместо этого. Я замечаю, что если я действительно пытаюсь использовать two в постоянном выражении, как в

 constexpr mat m = {{{{1,2}}, {{3,4}}}};
constexpr float val = two(m);
  

тогда MSVC действительно выдает несколько полезных сообщений об ошибках на этом этапе. То, что MSVC упустила возможность указать на проблему ранее, можно считать проблемой качества реализации.

Является ли возврат по значению здесь бесполезной пессимизацией, которую я должен удалить?

В показанном коде нет реальной причины не создавать все четыре operator[] функции constexpr . Объекты, которые не const могут использоваться в постоянном выражении, при условии, что их инициализация произошла во время того же самого постоянного выражения, и это может относиться к временному vec объекту, задействованному здесь. (Конечно, могут быть некоторые другие моменты, которые следует учитывать в более полном проекте, где вы обнаружили проблему, прежде чем упрощать ее для этого вопроса.)

Ответ №2:

Последний вопрос здесь заключается в том, что это проблема только потому, что const перегружает возвращаемое значение по значению, если они возвращаются по ссылке const, ни в одном компиляторе нет ошибок. Является ли возврат по значению здесь бесполезной пессимизацией, которую я должен удалить?

Этот вопрос начинается с ложного предположения. Это не «только проблема, потому что const перегружает возвращаемое значение по значению». Скорее, проблема возникает из-за постоянной перегрузки при mat::operator[] возврате чего-либо неконстантного, что приводит к тому, что компилятор соответствующим образом применяет неконстантную перегрузку vec::operator[] .

Если бы вы изменили const перегрузку mat::operator[] на следующую, она по-прежнему возвращала бы значение, но предупреждение gcc исчезло.

 constexpr const vec operator[](int index) const { return _x[index]; }
  

Это один из случаев, когда « constexpr const » не является избыточным. « constexpr » соответствует требованиям к функции, в то время как « const » определяет возвращаемый тип.

С другой стороны, если вы готовы возвращать по ссылке в неконстантной версии, почему бы не возвращать по ссылке в постоянной версии? Кто-то уже может получить ссылку, так чего же можно добиться, добавляя неэффективность копии в постоянную версию? (Я мог бы ожидать увидеть это наоборот: возвращать по значению для неконстантного, чтобы элемент не был изменен, но возвращать по ссылке const в версии const для повышения эффективности.)