Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 11:02
Оценка:
Есть код (прошу не спрашивать почему такой, это выжимка из большой библиотеки ):
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) ?
Отредактировано 07.08.2019 11:04 Videoman . Предыдущая версия .
//
Re: Вопрос про конструкторы
От: Stanislav V. Zudin Россия  
Дата: 07.08.19 11:47
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Как-то можно в списке инициализации сказать что нужно делать как в случае (1) ?


Скастить к нужному типу.
_____________________
С уважением,
Stanislav V. Zudin
Re[2]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 12:26
Оценка:
Здравствуйте, Stanislav V. Zudin, Вы писали:

SVZ>Скастить к нужному типу.


Пример можно ? Если static_cast — то не кастится, т.к. ambiguous...
Ну и вообще, от какста с указанием типа собственно и хотелось уйти, т.к. тип описывается очень длинно.
Отредактировано 07.08.2019 12:28 Videoman . Предыдущая версия .
Re[3]: Вопрос про конструкторы
От: Stanislav V. Zudin Россия  
Дата: 07.08.19 12:42
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Здравствуйте, Stanislav V. Zudin, Вы писали:


SVZ>>Скастить к нужному типу.


V>Пример можно ? Если static_cast — то не кастится, т.к. ambiguous...


#include <iostream>
#include <vector>
#include <string>

struct A
{
    operator size_t() const
    {
        return m_data.size();
    }

    operator std::vector<std::string>() const
    {
        return m_data;
    }

private:
    std::vector<std::string> m_data;
};

struct B
{
    B(const A& a) : v(static_cast<const std::vector<std::string>&>(a)) {}

    std::vector<std::string> v;
};


int main()
{
    std::cout << "Hello World!\n"; 
    A a;
    std::vector<std::string> v1 = a;
    std::vector<std::string> v2( static_cast<const std::vector<std::string>&>(a));

    B b(a);
    return 0;
}


Проверил на VS2017 и gcc-8.2.0

V>Ну и вообще, от какста с указанием типа собственно и хотелось уйти, т.к. тип описывается очень длинно.


Вообще не люблю каст операторы. Очень много сюрпризов несут в себе.
В 99% случаев можно сделать нормальные мембер функции, возвращающие нужные данные.
_____________________
С уважением,
Stanislav V. Zudin
Re[4]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 12:50
Оценка:
Здравствуйте, Stanislav V. Zudin, Вы писали:

SVZ>...

SVZ>Проверил на VS2017 и gcc-8.2.0

SVZ>Вообще не люблю каст операторы. Очень много сюрпризов несут в себе.

SVZ>В 99% случаев можно сделать нормальные мембер функции, возвращающие нужные данные.

Спасибо! Ну вот такого вот каста и хотелось бы избежать. В реальности у меня там очень навороченные и длинные шаблоны. А по поводу первого вопроса, можете пояснить? Может быть можно как-то исхитриться и не указывать весть тип в рамках С++17 ?
Re: Вопрос про конструкторы
От: sergii.p  
Дата: 07.08.19 12:55
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Есть код (прошу не спрашивать почему такой, это выжимка из большой библиотеки ):

V>
V>struct A
V>    {
V>        operator size_t() const;
V>        operator std::vector<std::string>() const;
V>    };

V>// Где-то ниже....

V>A a;
V>std::vector<string_t> v1 = a; // (1) ОК
V>std::vector<string_t> v2(a);  // (2) Error - Ambiguous...
V>


собственно проблема в том, что есть различные конструкторы 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 принимать никак не может. Поэтому тут неоднозначности не возникает
Отредактировано 07.08.2019 12:59 sergii.p . Предыдущая версия .
Re[2]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 13:12
Оценка:
Здравствуйте, 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) — что бы работало как мне нужно?
Re[3]: Вопрос про конструкторы
От: sergii.p  
Дата: 07.08.19 13:35
Оценка:
Здравствуйте, Videoman, Вы писали:

V>А как-нибудь можно добиться синтаксиса — v2(a) — что бы работало как мне нужно?


ну компилятор то уже сказал ответ. Синтаксис v2(a) невозможен.
Re[4]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 13:44
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>ну компилятор то уже сказал ответ. Синтаксис v2(a) невозможен.


В лоб — да, не может. Просто я надеялся что можно как-то, чуть изменив синтаксис, сказать что я хочу использовать вариант (1) — ну мало ли

Т.е. максимум что я могу сделать это — v2(a.As<decltype(v2)>()) — что-то такое ?
Просто странно что нет эквивалента std::vector<std::string> v2 = a, но для списка инициализации.
Отредактировано 07.08.2019 13:45 Videoman . Предыдущая версия .
Re: Вопрос про конструкторы
От: Constructor  
Дата: 07.08.19 13:49
Оценка:
Здравствуйте, 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++.
Re[2]: Вопрос про конструкторы
От: Constructor  
Дата: 07.08.19 13:54
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>Почему собственно код (1) компилируется. Там объект vector уже существует и никаких конструкторов не вызывается. Вызывается оператор присваивания. Этот оператор присваивания size_t принимать никак не может. Поэтому тут неоднозначности не возникает


Запись вида X x = y; не предполагает вызов оператора присваивания, а подразумевает выполнение copy initialization.
Re[2]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 13:58
Оценка:
Здравствуйте, Constructor, Вы писали:

C>Судя по тому, что свежие версии g++ (9.1.0) и clang++ (8.0.0) единодушно компилируют этот код с флагами -std=c++17 -Wall -Wextra -Werror -pedantic-errors, наблюдаемое поведение практически наверняка является багом в Microsoft Visual C++.


А вы не могли бы пояснить в чем бага и какой порядок подстановок должен быть по стандарту?
Re[3]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 14:01
Оценка:
Здравствуйте, Constructor, Вы писали:

C>Запись вида X x = y; не предполагает вызов оператора присваивания, а подразумевает выполнение copy initialization.


Я в курсе, просто забыл поправить коллегу. Да, но при copy_initialization совершенно другой порядок подстановок. Так и должно быть?
P.S. Уже глянул приведенную вами ссылку. Да, все верно, так и должно быть
Отредактировано 07.08.2019 14:09 Videoman . Предыдущая версия .
Re: Вопрос про конструкторы
От: andrey.desman  
Дата: 07.08.19 14:31
Оценка:
Здравствуйте, Videoman, Вы писали:

V>
V>struct A
V>    {
V>        operator size_t() const;
V>        operator std::vector<std::string>() const;
V>    };

V>// Где-то ниже....

V>A a;
V>std::vector<string_t> v1 = a; // (1) ОК
V>std::vector<string_t> v2(a);  // (2) Error - Ambiguous...
V>

V>Можно пояснить как логика тут работает (а-то я умудрился "пройти мимо")
V>В случае (1) — не можем найти конструктор из A() и вызывает оператор преобразования типа std::vector<std::string>. (Как точно называется это синтаксис?)
V>В случае (2) — пытаемся вызвать конструктор сразу и не можем выбрать преобразования, т.к. оба подходят.
V>У них действительно разная логика порядка поиска преобразований или я что-то путаю?

1 — это copy initialization, а 2 — это direct initialization.

Разница здесь в том, что (1) хочет вызвать конструктор копирования и пытается привести a к нужному типу (операторами преобразования или не-explicit конструкторами). А 2 смотрит все подходящие конструкторы, и среди
них выбирает наиболее подходящий.
Кстати, в C++17 тут не будет ошибки, потому как компилятор в обоих случаях предпочтет вызвать std::vector<std::string>(std::vector<std::string>&&).
Re[2]: Вопрос про конструкторы
От: andrey.desman  
Дата: 07.08.19 14:43
Оценка:
Здравствуйте, 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 все так же должна приводить к ошибке.
Re[2]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 14:45
Оценка:
Здравствуйте, 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.
Re[3]: Вопрос про конструкторы
От: andrey.desman  
Дата: 07.08.19 15:16
Оценка:
Здравствуйте, 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 слишком усердствует...
Re: Вопрос про конструкторы
От: night beast СССР  
Дата: 07.08.19 16:20
Оценка: +1
Здравствуйте, Videoman, Вы писали:

V>Есть код (прошу не спрашивать почему такой, это выжимка из большой библиотеки ):

V>
V>struct A
V>    {
V>        operator size_t() const;
V>        operator std::vector<std::string>() const;
V>    };
V>

V>Как-то можно в списке инициализации сказать что нужно делать как в случае (1) ?

std::vector<std::string> to_vector(const A&);

std::vector<std::string> v2(to_vector(a));
Отредактировано 07.08.2019 17:17 night beast . Предыдущая версия .
Re[2]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 07.08.19 17:51
Оценка:
Здравствуйте, night beast, Вы писали:

NB>
NB>std::vector<std::string> to_vector(const A&);

NB>std::vector<std::string> v2(to_vector(a));
NB>

Спасибо, но в моей ситуации точно не подойдет, т.к. в реалии у меня:
A — имеет шаблонный оператор приведения типа:
template <typename Type, std::enable_if<is_compatible_v<Type>>>
operator Type() const;

, а тип v2 может описывать капец каким длинным шаблоном — типа:
std::vector<std::vector<std::map<..... // тут легко может быть все что угодно любой степени влоденности

Самое обидное, что:
(1) [Type...] a = v2;    // (Copy initialization) Ok
(2) [Type...] a(v2);     // (Direct initialization) Error
(3) [Type...] a; a = v2; // (Assign) Ok
Re[3]: Вопрос про конструкторы
От: night beast СССР  
Дата: 07.08.19 18:06
Оценка:
Здравствуйте, Videoman, Вы писали:

NB>>
NB>>std::vector<std::string> to_vector(const A&);

NB>>std::vector<std::string> v2(to_vector(a));
NB>>

V>Спасибо, но в моей ситуации точно не подойдет, т.к. в реалии у меня:

неявные операторы приведения могут принести некоторые неожиданные проблемы.
вспомни пример каста в булу в optional.
Re: Вопрос про конструкторы
От: prog123 Европа  
Дата: 07.08.19 20:25
Оценка:
Здравствуйте, Videoman, Вы писали:

нужен не експлисит к-тор или експлисит к твоему классу. сделай свой класс от вектора, типа такого

#include <vector>
#include <string>
#include <iostream>

using StrVector = std::vector<std::string>;

struct A
{
    operator size_t() const { return data_.size(); }
    operator StrVector() const { return data_; }
    std::vector<std::string> data_;
};

struct MyVector: public StrVector
{
    explicit MyVector(A const& a) : StrVector(a.data_){}
};

int main()
{
    A a{{"one"}};
    //StrVector v2(a);
    StrVector v2{MyVector{a}};
    for(auto const& e: v2)
        std::cout << e << std::endl; 
    return 0;
}
Re[4]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 08.08.19 08:09
Оценка:
Здравствуйте, night beast, Вы писали:

NB>неявные операторы приведения могут принести некоторые неожиданные проблемы.

NB>вспомни пример каста в булу в optional.

Полностью с вами согласен, в общем не стоит так делать, но тут у меня подобие библиотеки сериализации, и там все-равно тип динамически проверяется и если-что летит исключение. Просто не хочется указывать типы два раза, особенно потому что они длинные. Хочется типа type lazy deduction.
Re[2]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 08.08.19 08:11
Оценка:
Здравствуйте, prog123, Вы писали:

P>...


Спасибо, но такое решение мне не подойдет. Я уже описал выше почему.
Re[5]: Вопрос про конструкторы
От: night beast СССР  
Дата: 08.08.19 08:22
Оценка:
Здравствуйте, Videoman, Вы писали:

NB>>неявные операторы приведения могут принести некоторые неожиданные проблемы.

NB>>вспомни пример каста в булу в optional.

V>Полностью с вами согласен, в общем не стоит так делать, но тут у меня подобие библиотеки сериализации, и там все-равно тип динамически проверяется и если-что летит исключение. Просто не хочется указывать типы два раза, особенно потому что они длинные. Хочется типа type lazy deduction.


можно сделать функцию, которая возвращает обертку над A, у которой будет только один оператор каста.
Re: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 08.08.19 08:24
Оценка:
Вроде нашел прототип решения:
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. не , поспешил. Все-таки осталась задача как вернуть константную ссылку на временный объект. Прокси при своем создании еще не знает тип который будут запрашивать
Отредактировано 08.08.2019 8:32 Videoman . Предыдущая версия . Еще …
Отредактировано 08.08.2019 8:30 Videoman . Предыдущая версия .
Отредактировано 08.08.2019 8:26 Videoman . Предыдущая версия .
Re[2]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 08.08.19 08:52
Оценка:
Даже вот такое чудо работает, но очень нужно мнение, насколько это правильно ???:
#include <string>
#include <vector>
#include <iostream>

template <typename Type>
    struct Stub
    {
        Stub(Type type) : type(type) {}

        operator const Type&() const { return type; };

        Type type;
    };

    struct A
    {
        operator const size_t& () const { return Stub<size_t>({}); }
        operator const std::vector<std::string>& () const { return Stub<std::vector<std::string>>({}); }
    };

    struct B
    {
        B(const A& a) : v(a) {}

        std::vector<std::string> v;
    };

int main()
{
    // Где-то ниже
    A a;
    std::vector<std::string> v1 = a; // (1)
    std::vector<std::string> v2(a);  // (2)
    v1 = a;                          // (3)
    
    B b(a);
    
}


P.S. Глянул ассемблерный листинг внимательно. Вроде не работает как нужно. Деструктор Stub вызывается перед выходом, до return .
Отредактировано 08.08.2019 9:21 Videoman . Предыдущая версия .
Re[3]: Вопрос про конструкторы
От: andrey.desman  
Дата: 08.08.19 09:13
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Самое обидное, что:

V>
V>(3) [Type...] a; a = v2; // (Assign) Ok
V>


А, кстати, почему бы не сделать просто присваиванием?
Пустой вектор конструируется бесплатно. Присваивание будет через move-assign, которое тоже считай бесплатно.
Re[4]: Вопрос про конструкторы
От: Videoman Россия https://hts.tv/
Дата: 08.08.19 09:19
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>А, кстати, почему бы не сделать просто присваиванием?

AD>Пустой вектор конструируется бесплатно. Присваивание будет через move-assign, которое тоже считай бесплатно.

Пока так и сделал. Проблемы только истетические. Ну и хотелось просто у коллег проконсультироваться не упустил ли чего.
Варинт (2) — v2(a) тоже работает, но не всегда, иногда возникает неоднозначность. Короче зависит от конструкторов класса v2.
Re[5]: Вопрос про конструкторы
От: antonio_v_krasnom Россия  
Дата: 09.08.19 10:15
Оценка:
Здравствуйте, Videoman, Вы писали:

SVZ>>Вообще не люблю каст операторы. Очень много сюрпризов несут в себе.

SVZ>>В 99% случаев можно сделать нормальные мембер функции, возвращающие нужные данные.

V>Спасибо! Ну вот такого вот каста и хотелось бы избежать. В реальности у меня там очень навороченные и длинные шаблоны. А по поводу первого вопроса, можете пояснить? Может быть можно как-то исхитриться и не указывать весть тип в рамках С++17 ?


Простое решение в лоб — это:
1. Если есть возможность вносить изменения в сам класс А, то вместо неявного преобразования к вектору (или плюс к неявному, если вместо будет ломать много кода) сделать явное: A::ToVector(), и его и вызывать. В идеале лучше вместо — меньше сюрпризов потом будет.
2. Если редактировать класс А нельзя (сторонняя библиотека или иные причины), то добавить обычную функцию
std::vector<std::string> AToVector(const A&);
и явно её вызывать.

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