Jump to content
    

Си++ 17, возвращаемый тип функции должен зависеть от значения аргумента

Добрый день!

Не могу решить задачу, если она решаемая. Chat GPT и Stack Overflow опрошены, но результаты мне не понравились.

// Настроечная структура содержит настройки для вольтметра и амперметра
struct Settings {
        VSettings voltmeter;
        ASettings ampermeter; // Типы VSettings и ASettings разные, не имеют ничего общего, но имеют одинаковые названия полей

        auto & get( int ch ) { // Хочу, чтобы этот метод возвращал разные структуры настройки в зависимости от величины аргумента
            if (ch < 3)
                return voltmeter;
            else
                return ampermeter;
        }
};

                       
// Использовать хочу так
Settings s;
s.get(chvalue).поле_структуры = некое_значение;

Это возможно на Си++ 17?)

Share this post


Link to post
Share on other sites

Template не вариант?

Share this post


Link to post
Share on other sites

1 minute ago, makc said:

Template не вариант?

Вариант. Но я не могу сам написать даже по мотивам того, что предложили в интернете. Нет полностью подходящего примера. Сможете подсказать?

Предлагается std::variant, но от требует проверки возвращаемого значения на принадлежность типу. А вариант с if constexpr меня не устраивает по причине переменного аргумента функции, который не известен на этапе компиляции.

Share this post


Link to post
Share on other sites

Опросил 3 нейронки, все выдали примерно одинаковый результат. Что не устраивает? 

Язык C++ - статически типизированный, т.е. все типы должны быть объявлены или вычислены на этапе компиляции. Если тип зависит от runtime-данных (как у вас, где значение `ch` может прилетать из космоса) - то это задача программиста императивно описать динамическую диспетчеризацию. 

1. Если `ch` может быть вычислен в compile-time (например, перебор от 1 до 8), то `ch` переносится в шаблонный параметр (а цикл заменяется через `std::integer_sequence` с fold expression). 

2. Если `ch` только в runtime, то явно пишем реализацию динамических типов. Вариант с `std::variant` и `std::visit` мне понравился - просто и понятно. Если же есть возможность управлять типами `VSettings` и `ASettings`, то наследование от общего родителя - самое оптимальное по потреблению ресурсов. 

Оффтопом: раньше работал с IAR EWARM 9.50, в котором была заявлена поддержка C++17. При этом в поставляемой стандартной библиотеке отсутствовал `std::apply` и еще несколько шаблонов. 

Share this post


Link to post
Share on other sites

4 часа назад, haker_fox сказал:
s.get(chvalue).поле_структуры = некое_значение;

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

Может, конечно, новомодные ++ что-то адекватное и предлагают, но мне видится тут один путь. Поскольку хочется полиморфизма -- получать разное поведение при одинаковых словах, и полиморфизм получается динамическим (т.е. на рантайме), то надо крутить в сторону виртуальных функций. Для того VSettings и ASettings должны быть потомками общего базового класса, имеющего виртуальные функции, возвращающие поля, а в потомках эти функции перепределить, чтобы каждая возвращала ссылку на собственное поле. Тогда должно получиться что-то вроде:

s.get(chvalue)->set_<поле_структуры>() = некое_значение;

Функция get() имеет тип возврата "указатель на базовый класс", а возвращает адрес на объект конкретного типа (V- или ASettings), а set_<поле_структуры>() -- это виртуальная функция, возвращающая ссылку на поле.

Вроде бы не сильно громоздко и сложно, по эффективности тоже должно быть норм, рантайм-накладные в любом случае какие-то будут.

Share this post


Link to post
Share on other sites

7 minutes ago, Anxigeros said:

Что не устраивает? 

Не устраивает отсутствие готового примера. Вы вот можете написать эту функцию? Я не могу. Я не очень хороший знаток Си++, чтобы сообразить, как это сделать по описаниям, которые вы привели.

8 minutes ago, Anxigeros said:

то наследование от общего родителя - самое оптимальное по потреблению ресурсов. 

Так и сделано пока. Но вот возникло желание попробовать мощь ООП во всей красе. Тем более, она уже понадобилась.

5 minutes ago, dxp said:

Вроде бы не сильно громоздко и сложно, по эффективности тоже должно быть норм, рантайм-накладные в любом случае какие-то будут.

Уф, спасибо!))) Это уже выше моего понимания, но я попробую разрбраться. Если не смогу, то оставлю пока как есть: две структуры имеют общего родителя.

12 minutes ago, Anxigeros said:

Если `ch` только в runtime, то явно пишем реализацию динамических типов. Вариант с `std::variant` и `std::visit` мне понравился - просто и понятно.

Да, это мой вариант. Но реализации не понравились, как я отметил выше)

Share this post


Link to post
Share on other sites

39 minutes ago, haker_fox said:

Но вот возникло желание попробовать мощь ООП во всей красе.

Вы пытаетесь на С++ писать, как на Питоне. Вряд ли получится что-то хорошее.

Share this post


Link to post
Share on other sites

30 minutes ago, novikovfb said:

Вы пытаетесь на С++ писать, как на Питоне.

Вы вопрос прочитали? Я напомню:

5 hours ago, haker_fox said:

Это возможно на Си++ 17?)

И да, я пока не пытаюсь писать. Я в отличие от тех, кто голословно что-то говорит про Питон, хотя бы задаю вопросы.

36 minutes ago, novikovfb said:

Вряд ли получится что-то хорошее.

Вы -- эксперт? Тогда наверняка у вас есть какие-то предложения чуть более ёмкие и полезные, чем пространственные изречения. Или нет?

Share this post


Link to post
Share on other sites

38 minutes ago, haker_fox said:

Вы -- эксперт? Тогда наверняка у вас есть какие-то предложения чуть более ёмкие и полезные, чем пространственные изречения. Или нет?

Штаны можно надеть через голову, но вряд ли это понравится. Прочитайте, как внутри С++ устроены и работают классы, наследование, определение типов, станет понятно, что это - совсем не Питон, где всё через строковые имена. Именно поэтому код на С++ работает гораздо быстрее (ему не нужно для каждого чиха сравнивать строки имен типов, строки имен полей и методов и т.д.), но поэтому сделать динамическое определение типа гораздо сложнее, чем в Питоне просто спросить у объекта, какого он класса.

Судя по вопросу, задача поставлена как-то странно. Как уже писали, для С++ у такой "фабрики классов" придется делать общего предка и наследование от него VSettings и ASettings, а также возвращать из функции не объект, а указатель на созданный в куче экземпляр нужного класса.

Share this post


Link to post
Share on other sites

#include <stdio.h>

struct XSettings {
};

struct VSettings : XSettings {
    int vol;
};

struct ASettings : XSettings {
    int amp;
};

struct Settings {
        VSettings voltmeter;
        ASettings ampermeter;

        XSettings & get( int ch ) { // Хочу, чтобы этот метод возвращал разные структуры настройки в зависимости от величины аргумента
            if (ch < 3)
                return voltmeter;
            else
                return ampermeter;
        }
};

int main()
{
    struct Settings settings = {
        .voltmeter = { .vol = 5},
        .ampermeter = { .amp = 6},
    };

    printf("\n");
    printf(" vol: %d\n", ((VSettings &)settings.get(1)).vol);
    printf(" amp: %d\n", ((ASettings &)settings.get(3)).amp);

    return 0;
}

image.png.8b9052d7c4418d1d419ca152da14eed5.png

Share this post


Link to post
Share on other sites

50 minutes ago, x893 said:
struct Settings {
        VSettings voltmeter;
        ASettings ampermeter;

        XSettings & get( int ch ) { // Хочу, чтобы этот метод возвращал разные структуры настройки в зависимости от величины аргумента
            if (ch < 3)
                return voltmeter;
            else
                return ampermeter;
        }
};

только после получения этого XSettings уже нет информации, это напряжение или ток. Т.е. 

  printf(" vol: %d\n", ((VSettings &)settings.get(3)).vol);

компилятор проглотит и выдаст какое-то значение, которое вряд ли понравится автору

Share this post


Link to post
Share on other sites

6 minutes ago, novikovfb said:

только после получения этого XSettings уже нет информации, это напряжение или ток. Т.е. 

  printf(" vol: %d\n", ((VSettings &)settings.get(3)).vol);

компилятор проглотит и выдаст какое-то значение, которое вряд ли понравится автору

В черном квадрате то, что выведет.

Share this post


Link to post
Share on other sites

5 minutes ago, x893 said:

В черном квадрате то, что выведет.

Я немного изменил Ваш текст в своем примере, смотрите внимательнее. Поэтому эта строка выдаст:

 vol: 6

сведений о фактическом типе возвращаемого значения нет, возьмет поле по смещению, соответствующему приведенному типу

Share this post


Link to post
Share on other sites

1 hour ago, novikovfb said:

Я немного изменил Ваш текст в своем примере, смотрите внимательнее. Поэтому эта строка выдаст:

 vol: 6

сведений о фактическом типе возвращаемого значения нет, возьмет поле по смещению, соответствующему приведенному типу

Меняйте, мне фиолетово.

1 hour ago, novikovfb said:

только после получения этого XSettings уже нет информации, это напряжение или ток. Т.е. 

  printf(" vol: %d\n", ((VSettings &)settings.get(3)).vol);

компилятор проглотит и выдаст какое-то значение, которое вряд ли понравится автору

Если знаний нет, то и результат будет какой угодно.

Share this post


Link to post
Share on other sites

11 часов назад, haker_fox сказал:

Предлагается std::variant, но от требует проверки возвращаемого значения на принадлежность типу.

Все так: в одном месте вы явно "запаковываете" информацию о типе (std::variant), а в другом месте - "распаковываете" (диспатчите, std::visit). И все руками, разумеется.

Спойлер
struct Settings {
    VSettings voltmeter;
    ASettings ampermeter;
  
    std::variant<VSettings*, ASettings*> get(int ch) {
        if (ch < 3)
            return &voltmeter;
        else
            return &ampermeter;
    }
};

Settings s{};

int main() {
    std::array channels = { 2, 4 };
    for (const auto chvalue : channels) {
        std::visit([=](auto* settings) {
            settings->digits = 10 + chvalue; 
        }, s.get(chvalue));
    }
    std::cout << "VSettings::digits = " << s.voltmeter.digits << '\n';
    std::cout << "ASettings::digits = " << s.ampermeter.digits << '\n';
}

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

Спойлер
struct Settings {
    VSettings voltmeter;
    ASettings ampermeter;
  
    template<typename F>
    void apply(int ch, F&& func) {
        if (ch < 3) 
            func(voltmeter);
        else 
            func(ampermeter);
    }
};

Settings s{};

int main() {
    std::array channels = { 2, 4 };
    for (const auto chvalue : channels) {
        s.apply(chvalue, [=](auto& settings) {
            settings.digits = 10 + chvalue;
        });
    }
    std::cout << "VSettings::digits = " << s.voltmeter.digits << '\n';
    std::cout << "ASettings::digits = " << s.ampermeter.digits << '\n';
}

 

11 часов назад, haker_fox сказал:

Но вот возникло желание попробовать мощь ООП во всей красе.

Если именно "мощь ООП", то только через интерфейсы!

Спойлер
struct ISettings {
    virtual ~ISettings() = default;
    virtual int& digits() = 0;
    virtual float& value() = 0;
};

struct VSettings : public ISettings {
    float voltage;
    int my_digits;
    int& digits() override { return my_digits; }
    float& value() override { return voltage; }
};

struct ASettings : public ISettings {
    float amperage;
    int my_digits;
    int& digits() override { return my_digits; }
    float& value() override { return amperage; }
};

struct Settings {
    VSettings voltmeter;
    ASettings ampermeter;
    
    ISettings& get(int ch) {
        if (ch < 3)
            return voltmeter;
        else
            return ampermeter;
    }
};

Settings s{};

int main() {
    std::array channels = { 2, 4 };
    for (const auto chvalue : channels) {
        s.get(chvalue).digits() = 10 + chvalue;
        s.get(chvalue).value() = 30 + chvalue * 3;
    }
    std::cout << "VSettings::digits = " << s.voltmeter.my_digits << '\n';
    std::cout << "VSettings::voltage = " << s.voltmeter.voltage << '\n';
    std::cout << "ASettings::digits = " << s.ampermeter.my_digits << '\n';
    std::cout << "ASettings::amperage = " << s.ampermeter.amperage << '\n';
}

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...