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