Есть код (прошу не спрашивать почему такой, это выжимка из большой библиотеки ):
struct A
{
operator size_t() const;
operator std::vector<std::string>() const;
};
// Где-то ниже....
A a;
std::vector<string_t> v1 = a; // (1) ОК
std::vector<string_t> v2(a); // (2) Error - Ambiguous...
Можно пояснить как логика тут работает (а-то я умудрился "пройти мимо")
В случае (1) — не можем найти конструктор из A() и вызывает оператор преобразования типа std::vector<std::string>. (Как точно называется это синтаксис?)
В случае (2) — пытаемся вызвать конструктор сразу и не можем выбрать преобразования, т.к. оба подходят.
У них действительно разная логика порядка поиска преобразований или я что-то путаю?
Какая возникает проблема:
struct B
{
B(const A& a) : v(a) {} // Тут используется логика и синтаксис варианта (2). Соответственно - Error - Ambiguous...
std::vector<std::string> v;
};
Как-то можно в списке инициализации сказать что нужно делать как в случае (1) ?
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>Скастить к нужному типу.
Пример можно ? Если static_cast — то не кастится, т.к. ambiguous...
Ну и вообще, от какста с указанием типа собственно и хотелось уйти, т.к. тип описывается очень длинно.
Здравствуйте, Videoman, Вы писали:
V>Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>>Скастить к нужному типу.
V>Пример можно ? Если static_cast — то не кастится, т.к. ambiguous...
Здравствуйте, Stanislav V. Zudin, Вы писали:
SVZ>... SVZ>Проверил на VS2017 и gcc-8.2.0
SVZ>Вообще не люблю каст операторы. Очень много сюрпризов несут в себе. SVZ>В 99% случаев можно сделать нормальные мембер функции, возвращающие нужные данные.
Спасибо! Ну вот такого вот каста и хотелось бы избежать. В реальности у меня там очень навороченные и длинные шаблоны. А по поводу первого вопроса, можете пояснить? Может быть можно как-то исхитриться и не указывать весть тип в рамках С++17 ?
собственно проблема в том, что есть различные конструкторы vector. Один принимает std::vector<std::string> (конструктор копирования), другой принимает параметр size_t — количество элементов в векторе. a одинаково хорошо приводится как к первому так и ко второму. Поэтому vector и кричит об Ambiguous.
Чтобы убрать неоднозначность можно вызвать явно оператор, который вы хотите.
std::vector<string_t> v2(a.operator std::vector<string>())
или
std::vector<string_t> v2(a.operator size_t())
p.s. Ну да. Почему собственно код (1) компилируется. Там объект vector уже существует и никаких конструкторов не вызывается. Вызывается оператор присваивания. Этот оператор присваивания size_t принимать никак не может. Поэтому тут неоднозначности не возникает
Здравствуйте, sergii.p, Вы писали:
SP>собственно проблема в том, что есть различные конструкторы vector. Один принимает std::vector<std::string> (конструктор копирования), другой принимает параметр size_t — количество элементов в векторе. a одинаково хорошо приводится как к первому так и ко второму. Поэтому vector и кричит об Ambiguous.
SP>Чтобы убрать неоднозначность можно вызвать явно оператор, который вы хотите. SP>std::vector<string_t> v2(a.operator std::vector<string>()) SP>или SP>std::vector<string_t> v2(a.operator size_t())
Это я понял. Собственно именно это в вопросе и описано. Даже касты подобрал что бы был видна неоднозначность.
SP>p.s. Ну да. Почему собственно код (1) компилируется. Там объект vector уже существует и никаких конструкторов не вызывается. Вызывается оператор присваивания. Этот оператор присваивания size_t принимать никак не может. Поэтому тут неоднозначности не возникает.
А как-нибудь можно добиться синтаксиса — v2(a) — что бы работало как мне нужно?
Здравствуйте, sergii.p, Вы писали:
SP>ну компилятор то уже сказал ответ. Синтаксис v2(a) невозможен.
В лоб — да, не может. Просто я надеялся что можно как-то, чуть изменив синтаксис, сказать что я хочу использовать вариант (1) — ну мало ли
Т.е. максимум что я могу сделать это — v2(a.As<decltype(v2)>()) — что-то такое ?
Просто странно что нет эквивалента std::vector<std::string> v2 = a, но для списка инициализации.
Здравствуйте, Videoman, Вы писали:
V>Можно пояснить как логика тут работает (а-то я умудрился "пройти мимо") V>В случае (1) — не можем найти конструктор из A() и вызывает оператор преобразования типа std::vector<std::string>. (Как точно называется это синтаксис?) V>В случае (2) — пытаемся вызвать конструктор сразу и не можем выбрать преобразования, т.к. оба подходят. V>У них действительно разная логика порядка поиска преобразований или я что-то путаю?
Судя по тому, что свежие версии g++ (9.1.0) и clang++ (8.0.0)единодушно компилируют этот код с флагами -std=c++17 -Wall -Wextra -Werror -pedantic-errors, наблюдаемое поведение практически наверняка является багом в Microsoft Visual C++.
Здравствуйте, sergii.p, Вы писали:
SP>Почему собственно код (1) компилируется. Там объект vector уже существует и никаких конструкторов не вызывается. Вызывается оператор присваивания. Этот оператор присваивания size_t принимать никак не может. Поэтому тут неоднозначности не возникает
Запись вида X x = y; не предполагает вызов оператора присваивания, а подразумевает выполнение copy initialization.
Здравствуйте, Constructor, Вы писали:
C>Судя по тому, что свежие версии g++ (9.1.0) и clang++ (8.0.0)единодушно компилируют этот код с флагами -std=c++17 -Wall -Wextra -Werror -pedantic-errors, наблюдаемое поведение практически наверняка является багом в Microsoft Visual C++.
А вы не могли бы пояснить в чем бага и какой порядок подстановок должен быть по стандарту?
Здравствуйте, Constructor, Вы писали:
C>Запись вида X x = y; не предполагает вызов оператора присваивания, а подразумевает выполнение copy initialization.
Я в курсе, просто забыл поправить коллегу. Да, но при copy_initialization совершенно другой порядок подстановок. Так и должно быть?
P.S. Уже глянул приведенную вами ссылку. Да, все верно, так и должно быть
V>Можно пояснить как логика тут работает (а-то я умудрился "пройти мимо") V>В случае (1) — не можем найти конструктор из A() и вызывает оператор преобразования типа std::vector<std::string>. (Как точно называется это синтаксис?) V>В случае (2) — пытаемся вызвать конструктор сразу и не можем выбрать преобразования, т.к. оба подходят. V>У них действительно разная логика порядка поиска преобразований или я что-то путаю?
Разница здесь в том, что (1) хочет вызвать конструктор копирования и пытается привести a к нужному типу (операторами преобразования или не-explicit конструкторами). А 2 смотрит все подходящие конструкторы, и среди
них выбирает наиболее подходящий.
Кстати, в C++17 тут не будет ошибки, потому как компилятор в обоих случаях предпочтет вызвать std::vector<std::string>(std::vector<std::string>&&).
Здравствуйте, Constructor, Вы писали:
C>Судя по тому, что свежие версии g++ (9.1.0) и clang++ (8.0.0)единодушно компилируют этот код с флагами -std=c++17 -Wall -Wextra -Werror -pedantic-errors, наблюдаемое поведение практически наверняка является багом в Microsoft Visual C++.
Это из-за copy elision, который появился в C++17. Компиляция в режиме -std=c++11 все так же должна приводить к ошибке.
Здравствуйте, andrey.desman, Вы писали:
AD>Разница здесь в том, что (1) хочет вызвать конструктор копирования и пытается привести a к нужному типу (операторами преобразования или не-explicit конструкторами). А 2 смотрит все подходящие конструкторы, и среди AD> них выбирает наиболее подходящий.
Не совсем так. В (1) случае тут copy_initialization и там там другие цепочка подбора. Здесь у меня как раз все ок.
Во втором случае direct_initialization и там правила как при вызове перегруженных функций.
AD>Кстати, в C++17 тут не будет ошибки, потому как компилятор в обоих случаях предпочтет вызвать std::vector<std::string>(std::vector<std::string>&&).
Можете ткнуть где это описано? В том то и парадокс, что у меня не работает по VS2017, который якобы С++17.
Здравствуйте, Videoman, Вы писали:
AD>>Разница здесь в том, что (1) хочет вызвать конструктор копирования и пытается привести a к нужному типу (операторами преобразования или не-explicit конструкторами). А 2 смотрит все подходящие конструкторы, и среди AD>> них выбирает наиболее подходящий. V>Не совсем так. В (1) случае тут copy_initialization и там там другие цепочка подбора. Здесь у меня как раз все ок. V>Во втором случае direct_initialization и там правила как при вызове перегруженных функций.
А я что написал?
AD>>Кстати, в C++17 тут не будет ошибки, потому как компилятор в обоих случаях предпочтет вызвать std::vector<std::string>(std::vector<std::string>&&). V>Можете ткнуть где это описано? В том то и парадокс, что у меня не работает по VS2017, который якобы С++17.
Copy elision.
First, if T is a class type and the initializer is a prvalue expression whose cv-unqualified type is the same class as T, the initializer expression itself, rather than a temporary materialized from it, is used to initialize the destination object: see copy elision
Про std::vector<std::string>(std::vector<std::string>&&) я наврал. copy elision как раз про прямую инициализацию, а не перемещение. Но тем не менее.
Мне не понятно про prvalue, если там вообще-то lvalue. Может, это наоборот gcc слишком усердствует...
Здравствуйте, night beast, Вы писали:
NB>неявные операторы приведения могут принести некоторые неожиданные проблемы. NB>вспомни пример каста в булу в optional.
Полностью с вами согласен, в общем не стоит так делать, но тут у меня подобие библиотеки сериализации, и там все-равно тип динамически проверяется и если-что летит исключение. Просто не хочется указывать типы два раза, особенно потому что они длинные. Хочется типа type lazy deduction.
Здравствуйте, Videoman, Вы писали:
NB>>неявные операторы приведения могут принести некоторые неожиданные проблемы. NB>>вспомни пример каста в булу в optional.
V>Полностью с вами согласен, в общем не стоит так делать, но тут у меня подобие библиотеки сериализации, и там все-равно тип динамически проверяется и если-что летит исключение. Просто не хочется указывать типы два раза, особенно потому что они длинные. Хочется типа type lazy deduction.
можно сделать функцию, которая возвращает обертку над A, у которой будет только один оператор каста.
struct A
{
operator const char&() const { return ch; }
operator const size_t&() const { return sz; }
operator const std::vector<std::string>&() const { return vec; }
char ch;
size_t sz;
std::vector<std::string> vec;
};
struct B
{
B(const A& a) : v(a) {}
std::vector<std::string> v;
};
// Где-то ниже
A a;
std::vector<string_t> v1 = a;
std::vector<string_t> v2(a);
B b(a);
На сомом деле такая штука работает, как ни странно. Т.к. у меня все-равно возвращается прокси шаблонный объект, то на практике типы ch, sz и vec сольются в один.
Остался вопрос к спецам, это норм, или опять глюки VS2017 ?
P.S. не , поспешил. Все-таки осталась задача как вернуть константную ссылку на временный объект. Прокси при своем создании еще не знает тип который будут запрашивать
Здравствуйте, Videoman, Вы писали:
V>Самое обидное, что: V>
V>(3) [Type...] a; a = v2; // (Assign) Ok
V>
А, кстати, почему бы не сделать просто присваиванием?
Пустой вектор конструируется бесплатно. Присваивание будет через move-assign, которое тоже считай бесплатно.
Здравствуйте, andrey.desman, Вы писали:
AD>А, кстати, почему бы не сделать просто присваиванием? AD>Пустой вектор конструируется бесплатно. Присваивание будет через move-assign, которое тоже считай бесплатно.
Пока так и сделал. Проблемы только истетические. Ну и хотелось просто у коллег проконсультироваться не упустил ли чего.
Варинт (2) — v2(a) тоже работает, но не всегда, иногда возникает неоднозначность. Короче зависит от конструкторов класса v2.
Здравствуйте, Videoman, Вы писали:
SVZ>>Вообще не люблю каст операторы. Очень много сюрпризов несут в себе. SVZ>>В 99% случаев можно сделать нормальные мембер функции, возвращающие нужные данные.
V>Спасибо! Ну вот такого вот каста и хотелось бы избежать. В реальности у меня там очень навороченные и длинные шаблоны. А по поводу первого вопроса, можете пояснить? Может быть можно как-то исхитриться и не указывать весть тип в рамках С++17 ?
Простое решение в лоб — это:
1. Если есть возможность вносить изменения в сам класс А, то вместо неявного преобразования к вектору (или плюс к неявному, если вместо будет ломать много кода) сделать явное: A::ToVector(), и его и вызывать. В идеале лучше вместо — меньше сюрпризов потом будет.
2. Если редактировать класс А нельзя (сторонняя библиотека или иные причины), то добавить обычную функцию
std::vector<std::string> AToVector(const A&);
и явно её вызывать.