Различная логика производится clang и gcc для одного и того же кода. Что правильно?

Я обнаружил несоответствие между логикой, созданной gcc-8 и clang-6.

Это произошло в реальной базе кода, когда я разработал с использованием clang, я развернул его с помощью gcc.

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

конспект

A неявно преобразуется в B A конструируем как из A (copy/move), так и из std::initializer_list<B>.

При инициализации A из A&&:

  • Clang выбирает Move-конструктор
  • gcc выбирает конструктор initializer_list.

живая демонстрация: https://coliru.stacked-crooked.com/a/bc50bd8f040d6476

MCVE

#include <initializer_list>
#include <utility>
#include <iostream>

struct thing;

struct thing_ref
{
    thing_ref(thing&& other) : ref_(other) {}
    thing_ref(thing& other) : ref_(other) {}

    thing& ref_;
};

struct thing
{
    thing() {}

    thing(std::initializer_list<thing_ref> things)
    {
        std::cout << "initializer_list path\n";
    }

    thing(thing&& other)
    {
        std::cout << "move path\n";
    }

    thing(thing const& other)
    {
        std::cout << "copy path\n";
    }
};

struct foo
{
    foo(thing t) : mything { std::move(t) } {}
    thing mything;
};

int main()
{
    thing t;

    auto f = foo { std::move(t) };
}

Настройки компилятора:

Ничего особенного, согласно ссылке coliru: -std=c++17 -O2

Ответ 1

Стандартный проект (T есть thing) [dcl.init.list]:

Инициализация списка - это инициализация объекта или ссылки из списка фигурных скобок....

Инициализация списка объекта или ссылки типа T определяется следующим образом:

  • Если braced-init-list содержит назначенный-initializer-list [не применяется]

  • Если T является агрегатным классом и [не применяется]

  • В противном случае, если T является массивом символов [не применяется]

  • В противном случае, если T является агрегатом [не применяется]

  • В противном случае, если список инициализаторов не имеет элементов [не применяется]

  • В противном случае, если T является специализацией std::initializer_list<E> [не применяется]

  • В противном случае, если T является типом класса, учитываются конструкторы. Применимые конструкторы перечислены и одним из лучших вариантов выбираются с помощью разрешения перегрузки [применяется]

  • ...

[over.match.list]:

Когда объекты неагрегированного класса типа T инициализируются списком так, что [dcl.init.list] указывает, что разрешение перегрузки выполняется в соответствии с правилами в этом подпункте, разрешение перегрузки выбирает конструктор в два этапа:

  • Первоначально функции-кандидаты являются конструкторами списка инициализаторов ([dcl.init.list]) класса T, а список аргументов состоит из списка инициализаторов как единственного аргумента. [применяется]

  • Если жизнеспособный конструктор списка инициализаторов не найден, снова выполняется разрешение перегрузки, где все функции-кандидаты являются конструкторами класса T, а список аргументов состоит из элементов списка инициализатора.

Если список инициализаторов не имеет элементов, а T имеет конструктор по умолчанию, первая фаза опускается. [не применяется]

Обратитесь к [dcl.init.list], чтобы узнать, что такое конструктор списка инициализатора:

Конструктор является конструктором списка инициализаторов, если его первый параметр имеет тип std::initializer_list<E> или ссылается на, возможно, cv-квалифицированный std::initializer_list<E> для некоторого типа E, и либо нет других параметров, либо все остальные параметры имеют аргументы по умолчанию ([dcl.fct.default]).

Есть также удобная заметка, которая подтверждает вывод:

Примечание: конструкторы списка инициализатора предпочтительнее других конструкторов в инициализации списка

Мой вывод:

Кандидат конструктора списка инициализатора должен рассматриваться первым и использоваться, если он действителен. Поскольку thing неявно преобразуется в thing_ref, она должна быть действительной. Мне кажется, что GCC соответствует.

Если вы хотите инициализировать объект типа, который имеет конструктор списка инициализаторов, но не хотите использовать этот конструктор, то не используйте инициализацию списка, то есть не используйте скобку-init-список.

Ответ 2

[over.match.list]/1:

(1) Когда объекты неагрегированного класса типа T инициализируются списком так, что [dcl.init.list] указывает, что разрешение перегрузки выполняется в соответствии с правилами в этом подпункте, разрешение перегрузки выбирает конструктор в два этапа:

(1.1) Первоначально функции-кандидаты являются конструкторами списка инициализаторов ([dcl.init.list]) класса T а список аргументов состоит из списка инициализаторов как единственного аргумента.

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

Так что Clang не прав, GCC в порядке.