如何在C++17中实现选项对象



在JavaScript中,有选项对象是相对常见的,其中所有选项都有默认值,并且只指定您需要的内容。一个经典的例子是旧的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编译器,所以我不能使用它们。

我能做什么?

使用C++17无法获得确切的语法,但可以使用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 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 },
});
}

好吧,您可以使用C++17 只使用编译时检查来完成这样的smth

#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;
}

更新

更多";优雅的";解决方案:

#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;
}

最新更新