C ++ 17: функция явного преобразования против явного конструктора + неявные преобразования - правила изменились? - PullRequest
0 голосов
/ 01 ноября 2018

Clang 6, clang 7 и gcc 7.1, 7.2 и 7.3 все согласны с тем, что следующее является допустимым кодом C ++ 17, но неоднозначно в C ++ 14 и C ++ 11. MSVC 2015 и 2017 также принимают это. Однако gcc-8.1 и 8.2 отклоняют его даже в режиме c ++ 17:

struct Foo
{
    explicit Foo(int ptr);
};

template<class T>
struct Bar
{
    operator T() const;
    template<typename T2>
    explicit operator T2() const;
};


Foo foo(Bar<char> x)
{
    return (Foo)x;
}

Компиляторы, принимающие его, выбирают функцию явного преобразования по шаблону, Bar::operator T2().

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

  1. функция явного преобразования Bar :: operator int ()
  2. сначала используется неявное пользовательское преобразование из Bar<char> в char, затем неявное встроенное преобразование из char в int, а затем явный конструктор Foo (int).

Итак, какой компилятор прав? В чем разница между стандартами C ++ 14 и C ++ 17?


Приложение : фактические сообщения об ошибках

Вот ошибка для gcc-8.2 -std=c++17. gcc-7.2 -std=c++14 печатает ту же ошибку:

<source>: In function 'Foo foo(Bar<char>)':    
<source>:17:17: error: call of overloaded 'Foo(Bar<char>&)' is ambiguous    
     return (Foo)x;    
                 ^    
<source>:3:14: note: candidate: 'Foo::Foo(int)'    
     explicit Foo(int ptr);    
              ^~~    
<source>:1:8: note: candidate: 'constexpr Foo::Foo(const Foo&)'    
 struct Foo    
        ^~~    
<source>:1:8: note: candidate: 'constexpr Foo::Foo(Foo&&)'

А вот ошибка от clang-7 -std=c++14 (clang-7 -std=c++17 принимает код):

<source>:17:12: error: ambiguous conversion for C-style cast from 'Bar<char>' to 'Foo'    
    return (Foo)x;    
           ^~~~~~    
<source>:1:8: note: candidate constructor (the implicit move constructor)    
struct Foo    
       ^    
<source>:1:8: note: candidate constructor (the implicit copy constructor)    
<source>:3:14: note: candidate constructor    
    explicit Foo(int ptr);    
             ^    
1 error generated.

1 Ответ

0 голосов
/ 01 ноября 2018

Здесь действуют несколько сил. Чтобы понять, что происходит, давайте посмотрим, куда нас приведет (Foo)x. Прежде всего, это приведение в стиле c эквивалентно static_cast в данном конкретном случае. И семантика статического приведения будет заключаться в прямой инициализации объекта результата. Поскольку объект результата будет иметь тип класса, [dcl.init] /17.6.2 говорит нам, что он инициализирован следующим образом:

В противном случае, если инициализация является прямой инициализацией, или если это инициализация копии, где cv-неквалифицированная версия источника тип тот же класс, или производный класс, класса назначения, конструкторы считаются. Применимые конструкторы перечисляются ([over.match.ctor]), а лучший выбирается через разрешение перегрузки. Выбранный конструктор называется инициализировать объект с помощью выражения инициализатора или список выражений в качестве аргумента (ов). Если конструктор не применяется, или Разрешение перегрузки неоднозначно, инициализация некорректна.

Таким образом, разрешение перегрузки, чтобы выбрать конструктор Foo для вызова. А если не удается разрешить перегрузку, программа работает некорректно. В этом случае он не должен завершиться ошибкой, даже если у нас есть 3 возможных конструктора. Это Foo(int), Foo(Foo const&) и Foo(Foo&&).

Для начала нам нужно скопировать инициализировать int в качестве аргумента в конструктор, а это значит найти неявную последовательность преобразования из Bar<char> в int. Поскольку пользовательский оператор преобразования, указанный вами с Bar<char> в char, не является явным, мы можем использовать его для неявной последовательности разговора Bar<char> -> char -> int.

Для двух других конструкторов нам нужно привязать ссылку к Foo. Однако мы не можем этого сделать. Согласно [over.match.ref] / 1 :

При условиях, указанных в [dcl.init.ref], ссылка может быть связаны непосредственно с glvalue или классом prvalue, который является результатом применение функции преобразования к выражению инициализатора. перегрузка Разрешение используется для выбора функции преобразования, которая будет вызвана. Предполагая, что «cv1 T» является базовым типом ссылки, являющейся инициализировано, и «cv S» является типом выражения инициализатора, с S типом класса функции-кандидаты выбираются следующим образом:

  • Рассматриваются функции преобразования S и его базовых классов. Те неявные функции преобразования, которые не скрыты в S и выдайте тип «lvalue ссылка на cv2 T2» (при инициализации lvalue ссылка или rvalue ссылка на функцию) или «cv2 T2» или «Rvalue ссылка на cv2 T2» (при инициализации rvalue ссылки или lvalue ссылка на функцию), где «cv1 T» совместимый с ссылками ([dcl.init.ref]) с «cv2 T2», является кандидатом функции. Для прямой инициализации, эти явные преобразования функции, которые не скрыты в S и дают тип «lvalue» ссылка на cv2 T2 »или« cv2 T2 »или« rvalue ссылка на cv2 T2 » соответственно, где T2 имеет тот же тип, что и T, или может быть преобразован в Тип T с квалификационным преобразованием ([conv.qual]), также функции-кандидаты.

Единственная функция преобразования, которая может дать нам glvalue или prvalue типа Foo, - это специализация указанного вами явного шаблона функции преобразования. Но, поскольку инициализация аргументов функции не является прямой инициализацией, мы не можем рассмотреть явную функцию преобразования. Поэтому мы не можем вызывать копирование или перемещать конструкторы в разрешении перегрузки. Это оставляет нас только с конструктором, принимающим int. Таким образом, разрешение перегрузки является успешным, и это должно быть так.

Тогда почему некоторые компиляторы находят егонеоднозначно, или вместо этого вызывать оператор шаблонного преобразования? Что ж, поскольку в стандарт было введено гарантированное разрешение копирования, было отмечено ( CWG выпуск 2327 ), что определяемые пользователем функции преобразования также должны способствовать разрешению копирования. Сегодня, согласно сухому письму стандарта, они этого не делают. Но мы бы очень хотели, чтобы они. Хотя формулировка того, как именно это должно быть сделано, все еще разрабатывается, может показаться, что некоторые компиляторы уже пытаются это реализовать.

И это та реализация, которую вы видите. Это противодействующая сила расширения разрешения копирования, которая мешает разрешению перегрузки здесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...