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

Неожиданная двусмысленность суррогатных функций вызова в С++

В следующем коде clang и EDG диагностируют неоднозначный вызов функции, в то время как gcc и Visual Studio принимают код.

struct s
{
    typedef void(*F)();
    operator F();       //#1
    operator F() const; //#2
};

void test(s& p)
{
    p(); //ambiguous function call with clang/EDG; gcc/VS call #1
}

В соответствии со стандартным проектом С++ (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3797.pdf) раздел 13.3.1.1.2 2 говорит:

суррогатная функция вызова с уникальной функцией вызова имени и имеющая функцию вызова R (идентификатор типа преобразования F, P1 a1,..., Pn an) {return F (a1,..., ап); } также рассматривается как функция-кандидат.

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

Я бы ожидал, что # 1 будет называться как с gcc и Visual Studio. Так что, если clang/EDG вместо этого отвергают вышеуказанный код, может кто-то пролить свет на причину, поскольку почему стандарт предусматривает, что в этом случае должна быть двусмысленность и , которая преимущества кода из этого свойства суррогатных функций вызова? Кто прав: clang (3.5)/EDG (310) или gcc (4.8.2)/VS (2013)?

4b9b3361

Ответ 1

Clang и EDG правы.

Вот как это работает. В стандарте говорится (тот же источник, что и ваша цитата):

Кроме того, для каждой неявной функции преобразования, объявленной в T формы

operator conversion-type-id () attribute-specifier-seq[opt] cv-qualifier ;

где [различные условия, выполненные в вашем примере], функция суррогатного вызова с уникальной функцией вызова имени и имеющая форму

R call-function ( conversion-type-id F, P1 a1, ... ,Pn an) { return F (a1, ... ,an); }

также рассматривается как функция-кандидат. [сделать то же самое для унаследованных преобразований] ^ 128

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

Следуя этой схеме, у вашего типа есть два оператора преобразования, которые дают два суррогата:

// for operator F();
void call-function-1(void (*F)()) { return F(); }
// for operator F() const;
void call-function-2(void (*F)()) { return F(); }

Ваш пример не содержит других кандидатов.

Затем компилятор выполняет перегрузочное разрешение. Поскольку сигнатуры двух функций вызова идентичны, он будет использовать одну и ту же последовательность преобразования для обоих - в частности, она будет использовать неконстантную перегрузку функции преобразования в обоих случаях! Таким образом, две функции не могут быть выделены, а вызов неоднозначен.

Ключом к пониманию этого является то, что преобразование, которое фактически используется при передаче объекта суррогату, не должно использовать функцию преобразования, сгенерированную суррогатом!

Я вижу два способа: GCC и MSVC могут прийти к неправильному ответу здесь.

Вариант 1 состоит в том, что они видят двух суррогатов с одинаковыми сигнатурами и каким-то образом объединяют их в один.

Вариант 2, скорее всего, заключается в том, что они думали: "Эй, нам не нужно делать дорогостоящий поиск конверсии для объекта здесь, мы уже знаем, что он будет использовать функцию преобразования, которая была сгенерирована суррогатом для". Это похоже на оптимизацию звуков, за исключением случая этого края, где это предположение неверно. В любом случае, привязывая преобразование к функции преобразования источника, один из суррогатов использует идентификатор-идентификатор пользователя как последовательность преобразования для объекта, в то время как другой использует идентификатор const-user, что делает его хуже.