简体   繁体   中英

How can I implement option objects in C++17?

In JavaScript it's relatively common to have option objects , where all the options have default values and you only specify what you need. A classic example is the old jQuery.ajax function:

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

I'm aware of C++20 designated initializers , a feature borrowed from C99, which solves this problem. However, I'm using a C++17 compiler, so I can't use them.

What can I do?

You can't get that exact syntax with C++17, but you could get close by using a std::map to bundle up the arguments into a dictionary. Also using C++17's std::any to allow the value to be whatever is appropriate.

#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 const&);
};

void JQuery::ajax(dict_t const& 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 },
    });
}

Well, you can do smth like this using only compile-time checks with 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(Type&& 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)&& first_value, decltype(std::declval<class_type>().*Members)&&... 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_type& object, decltype(std::declval<class_type>().*Members)&&... args) noexcept;

    template<>
    static void initialize_by_ref(class_type& object) noexcept {}

    template<auto class_type::*FirstMember, auto... Members>
    static void initialize_by_ref(class_type& object, decltype(std::declval<class_type>().*FirstMember)&& first_value, decltype(std::declval<class_type>().*Members)&&... 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<
        &Data::m_b,
        &Data::m_c2,
        &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;
}

Update

More "elegant" solution:

#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(Pairs&&... pairs) noexcept;

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

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

    template<typename... Pairs>
    static void initialize(class_type& object, Pairs&&...) noexcept;

    template<>
    static void initialize(class_type& object) noexcept {}

    template<typename Pair, typename... Pairs>
    static void initialize(class_type& object, Pair&& pair, Pairs&&... 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>&... pairs) noexcept {
        ajax(initializer<Object>::create(pairs...));
    }

    template<class Object>
    static void ajax(Object&& 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 {&Data::m_b, true},
        std::pair {&Data::m_str, "new text"},
        std::pair {&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 {&Data::m_b, true},
        std::pair {&Data::m_str, "new text"},
        std::pair {&Data::m_c2, c2}
    );

    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM