Как я могу реализовать объекты опций в C 17?

#c #parameters #c 17

#c #параметры #c 17

Вопрос:

В JavaScript относительно часто встречаются объекты option, где все параметры имеют значения по умолчанию, и вы указываете только то, что вам нужно. Классическим примером является старая функция jQuery.ajax:

 jQuery.ajax({
    url: "https://google.com",
    cache: true,
    timeout: 500
    // all other parameters are left to default values
});
  

Я знаю о назначенных инициализаторах C 20, функции, заимствованной из C99, которая решает эту проблему. Однако я использую компилятор C 17, поэтому я не могу их использовать.

Что я могу сделать?

Комментарии:

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

2. @Eljay Чтобы сделать это, мне пришлось бы объявить структуру, а затем передать структуру в функцию. Я ищу способ сделать это встроенным, как в приведенном примере.

3. Некоторые используют boost.org/doc/libs/1_74_0/libs/parameter/doc/html/index.html

Ответ №1:

Вы не можете получить такой точный синтаксис с помощью C 17, но вы могли бы приблизиться к этому, используя a std::map для объединения аргументов в словарь. Также используется C 17 std::any , чтобы значение могло быть любым подходящим.

 #include <any>
#include <iostream>
#include <map>
#include <string>

using std::any;
using std::cout;
using std::map;
using std::string;
using std::string_literals::operator""s;

using dict_t = map<string, any>;

struct JQuery {
    void ajax(dict_t constamp;);
};

void JQuery::ajax(dict_t constamp; dict) {
    auto url_it = dict.find("url");
    auto url = url_it != dict.end() ? std::any_cast<string>(url_it->second) : "default_url"s;

    auto cache_it = dict.find("cache");
    auto cache = cache_it != dict.end() ? std::any_cast<bool>(cache_it->second) : false;

    auto timeout_it = dict.find("timeout");
    auto timeout = timeout_it != dict.end() ? std::any_cast<int>(timeout_it->second) : 60;

    cout << "url: " << url << "n";
    cout << "cache: " << (cache ? "true" : "false") << "n";
    cout << "timeout: " << timeout << "n";
}

int main() {
    JQuery jQuery;
    jQuery.ajax({
        { "url", "https://google.com"s },
        { "cache", true },
        { "timeout", 500 },
    });
}
  

Комментарии:

1. Он действительно выполняет свою работу, но это довольно дорогой синтаксический сахар.

2. @rodrigocfd: Нет, это точно так же дорого, как версия JavaScript. Ну, JS может создать хеш-таблицу для выполнения поиска ключа / значения (вы могли бы использовать unordered_map ), но в противном случае оба создают произвольное сопоставление ключей со значениями. Разница в том, что C заставляет вас произносить это по буквам; у него нет удобного синтаксиса для этого.

Ответ №2:

Ну, вы можете сделать что-то подобное, используя только проверки во время компиляции с C 17

 #include <iostream>

#include <type_traits>

template<class Class>
struct custom_initializer {
    using class_type = typename std::remove_cv_t<std::remove_reference_t<Class>>;

    constexpr custom_initializer() = default;

    template<auto Class::*MemberPtr, typename Type = decltype(std::declval<Class>().*MemberPtr)>
    static class_type set(Typeamp;amp; value) noexcept {
        static_assert(std::is_member_object_pointer_v<decltype(MemberPtr)>, "accept only pointers to members");
        class_type res;
        res.*MemberPtr = std::forward<Type>(value);
        return res;
    }

    template<auto class_type::*FirstMember, auto... Members>
    static class_type initialize(decltype(std::declval<class_type>().*FirstMember)amp;amp; first_value, decltype(std::declval<class_type>().*Members)amp;amp;... args) noexcept {
        class_type res;
        initialize_by_ref<FirstMember, Members...>(res, std::forward<decltype(std::declval<class_type>().*FirstMember)>(first_value), std::forward<decltype(std::declval<class_type>().*Members)>(args)...);
        return res;
    }

    template<auto... Members>
    static void initialize_by_ref(class_typeamp; object, decltype(std::declval<class_type>().*Members)amp;amp;... args) noexcept;

    template<>
    static void initialize_by_ref(class_typeamp; object) noexcept {}

    template<auto class_type::*FirstMember, auto... Members>
    static void initialize_by_ref(class_typeamp; object, decltype(std::declval<class_type>().*FirstMember)amp;amp; first_value, decltype(std::declval<class_type>().*Members)amp;amp;... args) noexcept {
        static_assert(std::is_member_object_pointer_v<decltype(FirstMember)>, "accept only pointers to members");
        object.*FirstMember = std::forward<decltype(std::declval<class_type>().*FirstMember)>(first_value);
        initialize_by_ref<Members...>(object, std::forward<decltype(std::declval<class_type>().*Members)>(args)...);
    }
};

struct Data {
    char m_c1 = '0',
        m_c2 = 'c',
        m_c3 = '\';
    bool m_b = false;
    std::string m_str = "some text";
};

int main(int argc, char const *argv[]) {
    auto data = custom_initializer<Data>::initialize<
        amp;Data::m_b,
        amp;Data::m_c2,
        amp;Data::m_str
    >(true, '4', "new value");

    std::cout << data.m_b << std::endl;
    std::cout << data.m_c2 << std::endl;
    std::cout << data.m_str << std::endl;

    return 0;
}

  

Обновить

Более «элегантное» решение:

 #include <iostream>

#include <type_traits>

template<class Class>
struct initializer {
    using class_type = typename std::remove_cv_t<std::remove_reference_t<Class>>;

    static_assert(std::is_default_constructible_v<class_type>, "initializer template argument must be default constructible class (struct)");

    initializer() = delete;

    template<typename... Pairs>
    static class_type create(Pairsamp;amp;... pairs) noexcept;

    template<>
    static class_type create() noexcept {
        return class_type{};
    }

    template<typename Pair, typename... Pairs>
    static class_type create(Pairamp;amp; pair, Pairsamp;amp;... pairs) noexcept {
        class_type resu<
        initialize(result, std::forward<Pair>(pair), std::forward<Pairs>(pairs)...);
        return resu<
    }

    template<typename... Pairs>
    static void initialize(class_typeamp; object, Pairsamp;amp;...) noexcept;

    template<>
    static void initialize(class_typeamp; object) noexcept {}

    template<typename Pair, typename... Pairs>
    static void initialize(class_typeamp; object, Pairamp;amp; pair, Pairsamp;amp;... pairs) noexcept {
        using first_value_type = std::remove_reference_t<decltype(std::get<0>(std::declval<Pair>()))>;
        using second_value_type = decltype(std::get<1>(std::declval<Pair>()));

        static_assert(std::is_member_object_pointer_v<first_value_type>, "");
        static_assert(std::is_convertible_v<std::remove_reference_t<second_value_type>, std::remove_reference_t<decltype(std::declval<class_type>().*std::declval<first_value_type>())>>, "");

        object.*(std::get<0>(pair)) = std::forward<second_value_type>(std::get<1>(pair));
        initialize(object, std::forward<Pairs>(pairs)...);
    }
};

struct JQuery {

    template<class Object, typename... Args1, typename... Args2>
    static void ajax(const std::pair<Args1, Args2>amp;... pairs) noexcept {
        ajax(initializer<Object>::create(pairs...));
    }

    template<class Object>
    static void ajax(Objectamp;amp; object) noexcept {
        // ... do smth
        std::cout << "initialization completed" << std::endl;
    }
};


struct Data {
    char m_c1 = '0',
        m_c2 = 'c',
        m_c3 = '\';
    bool m_b = false;
    std::string m_str = "some text";
};

int main(int argc, char const *argv[]) {
    char c2 = 'w';
    Data data = initializer<Data>::create(
        std::pair {amp;Data::m_b, true},
        std::pair {amp;Data::m_str, "new text"},
        std::pair {amp;Data::m_c2, c2}
    );

    std::cout << data.m_b << std::endl;
    std::cout << data.m_str << std::endl;
    std::cout << data.m_c2 << std::endl;

    // same but with "JQuery" mock

    JQuery::ajax<Data>(
        std::pair {amp;Data::m_b, true},
        std::pair {amp;Data::m_str, "new text"},
        std::pair {amp;Data::m_c2, c2}
    );

    return 0;
}