cpp-ru / ideas

Идеи по улучшению языка C++ для обсуждения
https://cpp-ru.github.io/proposals
Creative Commons Zero v1.0 Universal
89 stars 0 forks source link

Неявное приведение std::pair к структуре. #532

Open IvanOrfanidi opened 1 year ago

IvanOrfanidi commented 1 year ago

Неявное приведение std::pair к структуре.

Возможное продолжение с structured binding и упрощение синтаксиса С++. Приведение будет работать если типы структуры и pair совпадают, ну и логично количество элементов только 2.

Например:

int main() {
    std::map<std::string, unsigned> all_data = {
      {"GPS", 14},
      {"GLONASS", 10},
      {"GALILEO", 8},
    };

    struct GPS_Data {
        std::string service{};
        unsigned num_of_sat = 0;
    };
    GPS_Data gps = *all_data.begin();

    std::cout << "Service: " << gps.service << ", Number of satellites: " << gps.num_of_sat << std::endl;
}

Вместо:

    const auto& [service, num_of_sat] = *all_data.begin();
    GPS_Data gps = {service, num_of_sat};

Возможные проблемы: Пониманием кода когда структура содержит один элемент std::pair.

    struct GPS_Data {
        std::pair<std::string, unsigned> data;
    };
    GPS_Data gps = {*all_data.begin()};

Тогда запись: GPS_Data gps = {*all_data.begin()}; против GPS_Data gps = *all_data.begin(); может вызывать неоднозначность.

eoan-ermine commented 1 year ago

Если неявное приведение std::pair — то и std::tuple тоже? К чему ограничение на два элемента?

Предполагается, что GPS_Data gps = *all_data.begin() семантически эквивалентно GPS_Data gps = {std::get<0>(begin_element), std::get<1>(begin_element}), а GPS_Data gps = std::move(*all_data.begin()} таким образом должно быть семантически эквивалентно GPS_Data gps = {std::get<0>(std::move(begin_element)), std::get<1>(std::move(begin_element}))?

Как предлагается обрабатывать случаи, когда существует конструктор копирования/перемещения для кортежа?

IvanOrfanidi commented 1 year ago

По поводу приведения std::tuple хорошая идея. Если честно не знаю можно ли на этапе компиляции выяснить кол-во и тип std::tuple не будет ли в этом скрытых проблем.

По поводу конструктор копирования/перемещения, можно сделать как это сделано в structured binding. по ссылке const auto& [service, num_of_sat] = *all_data.begin(); или по значению auto [service, num_of_sat] = *all_data.begin(); Там же нет проблем с конструктором копирования/перемещения для std::pair.

Речь идет просто о "синтаксическом сахаре", чтобы не писать отдельный конструктор копирования/перемещения для структуры. Или не пользоваться дополнительными объявлениями переменных как ниже:

const auto& [service, num_of_sat] = *all_data.begin();
GPS_Data gps = {service, num_of_sat};

или так

GPS_Data gps = {all_data.begin()->first, all_data.begin()->second};
pavelkryukov commented 1 year ago

Можно определить небольшой конвертер:

template<typename T, typename ... Args, size_t ... Indices>
T tuple_cast_impl(std::index_sequence<Indices...>, const std::tuple<Args...>& t)
{
    return T{std::get<Indices>(t)...};
}

template<typename T, typename ... Args, size_t ... Indices>
T tuple_cast_impl(std::index_sequence<Indices...>, const std::pair<Args...>& t)
{
    return T{std::get<Indices>(t)...};
}

template<typename T, typename ... Args>
T tuple_cast(const std::tuple<Args...>& t)
{
    return tuple_cast_impl<T>(std::make_index_sequence<sizeof...(Args)>{}, t);
}

template<typename T, typename ... Args>
T tuple_cast(const std::pair<Args...>& t)
{
    return tuple_cast_impl<T>(std::make_index_sequence<sizeof...(Args)>{}, t);
}

и тогда с ним будет работать, при необходимости можно перенести в конструктор.

    auto gps = tuple_cast<GPS_Data>(*all_data.begin());
XRay3D commented 1 year ago

есть такой вариант. Я нечто похожее у себя писал.


template <typename S>
struct cast {
    cast(const S& s)
        : s {s} { }
    template <typename T>
    operator T() const { return impl<T>(std::make_index_sequence<std::tuple_size_v<S>> {}); }
    template <typename T>
    T to() const { return impl<T>(std::make_index_sequence<std::tuple_size_v<S>> {}); }

private:
    const S& s;
    template <typename T, size_t... Is>
    T impl(std::index_sequence<Is...>) const { return {std::get<Is>(s)...}; }
};

GPS_Data gps; 

gps = cast(variable);
auto gps2 = cast(variable).to<GPS_Data>();
pavelkryukov commented 1 year ago

Возможно, стоит обьединить с #485, где более жёсткие требования: делать неявное приведение и для массивов, передвигаемых std::memcpy.