Неоднозначность перегрузки C++ std::vector initializer_list (g++/clang++)

Рассмотрим следующий код:

#include <vector>

#define BROKEN

class Var {
public:
#ifdef BROKEN
    template <typename T>
    Var(T x) : value(x) {}
#else
    Var(int x) : value(x) {}
#endif

    int value;
};

class Class {
public:
    Class(std::vector<Var> arg) : v{arg} {}

    std::vector<Var> v;
};

Clang++ (7.0.1) компилирует это без ошибок независимо от того, определен ли BROKEN, но g++ (8.2.1) выдает ошибку, если BROKEN определен:

main.cpp:9:20: error: cannot convert ‘std::vector<Var>’ to ‘int’ in initialization
  Var(T x) : value(x) {}
                    ^

Насколько мне известно, юниформ-инициализация, используемая здесь, должна выбирать конструктор std::vector(std::vector&&) в обоих случаях; по-видимому, однако, g++ вместо этого видит {arg} как список инициализаторов и пытается инициализировать первый элемент v с помощью Var, примененного к вектору, что не сработает.

Если BROKEN не определено, g++, по-видимому, достаточно умен, чтобы понять, что перегрузка initializer_list не будет работать.

Какое поведение является правильным или оба разрешены стандартом?

СЛОМАН определенный gcc
BROKEN не определен gcc
СЛОМАН определенный лязг

Я не эксперт, но есть интересные данные: если вы пометите ctor explicit, то g++ понравится больше.

Thomas 11.03.2019 11:32

Еще одна точка данных: godbolt.org/z/gsc48J (вы можете поиграть с разными уровнями оптимизации). По-видимому, gcc вызывает Var::Var<std::vector<Var> >(std::vector<Var>) после вызова конструктора копирования std::vector.

chtz 11.03.2019 12:07

По той же причине, что и эта почта.

xskxzr 11.03.2019 12:32

@xskxzr Если я правильно понимаю, это означает, что clang считает ошибку типа в списке инициализации (: value(x)) означающей, что конструктор нежизнеспособен, в то время как gcc уже решил, что конструктор жизнеспособен, прежде чем смотреть на список инициализации. Учитывая, что список инициализации, как правило, может находиться в другой единице компиляции, поведение gcc кажется подходящим, что делает clang неверным. Это правда?

tomsmeding 11.03.2019 14:16

@tomsmeding: Clang не учитывает template <typename T> Var(T x), даже если вы удалите тело функции. Сообщение, на которое ссылается @xskxzr, не содержит никаких возможных ошибок типов внутри конструктора или списка инициализаторов.

chtz 13.03.2019 11:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
10
5
334
1

Ответы 1

Вот два способа решить эту проблему:

class Var {
public:
#ifdef BROKEN
    template <typename T>
    Var(T x, typename std::enable_if<std::is_convertible<T, int>::value>::type* = nullptr) : value(x) {}
#else
    Var(int x) : value(x) {}
#endif

    int value;
};

Живая демонстрация.

Или:

class Class {
public:
    Class(std::vector<Var> arg) : v(arg) {}

    std::vector<Var> v;
};

Живая демонстрация.

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

Таким образом, добавление правильного enable_if решает проблему, поскольку защищает от создания версии списка инициализации конструктора. В концепциях С++ 20 это лучше решается.

А когда вместо фигурных скобок для инициализации используются v список инициализации не предпочтителен.

Я не уверен, кто прав (ИМХО лязг, но это просто внутреннее чувство). Может кто лучше разбирается в стандарте подскажет.

Как template <typename T> Var(T x); будет аргументом в пользу SFINAE? Я не думаю, что тело функции (или список инициализаторов) имеет значение, тем более что оно может находиться в другом модуле компиляции.

chtz 13.03.2019 11:16

Другие вопросы по теме