Подтвердить что ты не робот

С++ 11: плохо сформированные вызовы - это поведение undefined?

В разделе 14.6.4.2 из N3485 говорится следующее о поиске зависимых кандидатских функций:

Если вызов будет плохо сформирован или найдет лучшее совпадение, поиск в связанных пространствах имен рассмотрит все объявления функций с внешней связью, введенные в эти пространства имен во всех единицах перевода, а не только с учетом тех объявлений, которые найдены в шаблоне контекста определения контекста и шаблона, то программа имеет поведение undefined.

Что именно означает, что вызов будет "плохо сформирован", и как будет вызван неправильный вызов путем поиска? Кроме того, почему имеет значение, что лучшее совпадение было бы найдено, если бы рассматривались все единицы перевода?

4b9b3361

Ответ 1

Что именно означает, что вызов "плохо сформирован"

Формально, плохо сформированный определяется как [defns.ill.formed] как не правильно сформированный, а хорошо сформированная программа определяется [defns.well.formed] как:

Программа на С++, построенная в соответствии с правилами синтаксиса, диагностическими семантическими правилами и Правилом определения (3.2).

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

как будет вызван неправильный вызов путем поиска?

Я думаю, что он говорит: "Если (вызов был бы плохо сформирован, то нашел бы лучшее совпадение) имел бы поиск в соответствующих пространствах имен, рассматриваемых во всех объявлениях функций с внешней привязкой...", что означает, что у вас есть undefined, если рассматривать другие функции, найдут равные или лучшие совпадения. В равной степени хорошие совпадения сделают вызов неоднозначным, то есть плохо сформированным и лучшим совпадением, приведет к тому, что будет вызвана другая функция.

Итак, если в другом контексте вызов был бы неоднозначным или вызвал бы другую ошибку, но преуспел только из-за ограниченного набора имен в контекстах создания и определения, он undefined. И если в другом контексте вызов бы выбрал лучшее совпадение, что также undefined.

Кроме того, почему имеет значение, что лучшее совпадение было бы найдено, если бы рассматривались все единицы перевода?

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

Это похожее (если и не охвачено) последнее предложение предыдущего абзаца:

Специализация для любого шаблона может иметь точки инстанцирования в нескольких единицах перевода. Если две разные точки инстанцирования дают шаблонную специализацию разных значений в соответствии с одним правилом определения (3.2), программа плохо сформирована, не требуется диагностика.

Страница 426 С++ ARM (Ellis and Stroustrup) дает немного контекста для этого текста (и я считаю, что и для 14.6.4.2), и объясняет это более кратко и четко, чем я сделал выше:

Это, по-видимому, подразумевает, что глобальное имя, используемое внутри шаблона, может быть привязано к различным объектам или функциям в разных единицах компиляции или даже в разных точках единицы компиляции. Однако, если это произойдет, результирующая функция или класс шаблона становятся незаконными с помощью правила "одного определения" (§7.1.2).

Там также другая соответствующая формулировка тех же правил в [basic.def.odr]/6

Ответ 2

Проблема состоит в том, что пространства имен могут быть определены по частям, поэтому нет места one, которое гарантированно определяет всех членов пространства имен. В результате различные единицы перевода могут видеть разные наборы элементов пространства имен. В этом разделе говорится, что если часть, которая не была замечена, повлияет на поиск, поведение undefined. Например:

namespace mine {
    void f(double);
}

mine::f(2); // seems okay...

namespace mine {
    void f(char);
}

mine::f(2); // ambiguous, therefore ill-formed

В правиле говорится, что первый вызов f(2) вызывает поведение undefined, потому что он был бы плохо сформирован, если бы все перегрузки в mine были видны в этой точке.

Ответ 3

Основываясь на частичном ответе @tletnes, я думаю, что я придумал простую программу, которая запускает это поведение undefined. Конечно, он использует несколько единиц перевода.

cat >alpha.cc <<EOF
#include <stdio.h>
void customization_point(int,int) { puts("(int,int)"); }
#include "beta.h"
extern void gamma();
int main() {
    beta(42);
    gamma();
}
EOF

cat >gamma.cc <<EOF
#include <stdio.h>
void customization_point(int,double) { puts("(int,double)"); }
#include "beta.h"
void gamma() { beta(42); }
EOF

cat >beta.h <<EOF
template<typename T>
void beta(T t) {
    customization_point(t, 3.14);
}
EOF

Компиляция этой программы с разными уровнями оптимизации изменяет ее поведение. Это нормально, в соответствии со стандартом, потому что вызов в "alpha.cc" вызывает поведение undefined.

$ clang++ alpha.cc gamma.cc -O1 -w ; ./a.out
(int,int)
(int,int)
$ clang++ alpha.cc gamma.cc -O2 -w ; ./a.out
(int,int)
(int,double)

Ответ 4

Когда я читаю это правило, я предполагаю, что код, похожий на следующий, является, по крайней мере, частью того, что рассматривалось:

int foo(int a; int b){ printf("A"); }

int main(){
   foo(1, 1.0);
}

int foo(int a, double b){ printf("B"); }

или

int foo(int a);

int main(){
   foo(1);
}

int foo(int a, double b){ printf("B"); }