Given a list of types, names, and default values, I could easily write a tool that generates valid c++ code that declares a class with a member variable of each type, name and default value. For example, given the list
(and class name "Baz"), it would generate
class Baz {
int foo = 42;
float bar = 0.1f;
}
If a tool could generate such a class, couldn't the compiler do it for me? I'm thinking about something along these lines (note: this is pseudocode):
template <typename ...MemberTypes> class Baz {
MemberTypes::type... MemberTypes::name... = MemberTypes::default...;
}
Above class would be created something like
using MyBaz = Baz<member_type<int, "foo", 42>, member_type<float, "bar", 0.1f>>;
Reasons why this might be possible:
Reasons why it might be impossible:
If this is possible, how would it be done? If it isn't possible, why not? Does the upcoming c++17 change anything in this respect?
Update: Example Problem:
Often, configuration data is stored as hierarchy of strings or some other form of "any type". This, however, leads to ugly code (config.get<int>("core.timeout")
) and prevents the compiler from helping out with, for example, typos (config.get<int>("core.timeuot")
).
By declaring every configuration variable with its true type, the compiler can check types and prevent spelling-errors. However, then there needs to be custom code to read configuration data into the right member variables. If new configuration switches are added, it is easy to forget updating this code.
It would be convenient to just specify the types and names of all members, and then let the compiler auto-generate the class (including a method to read a config file). This is a possible use-case for the functionality I asked for.
It's not possible to define the names of class members using templates and string literals are challenging to use with templates. However, you can achieve something close to what you want if you are willing to use types as member identifiers instead.
I suggest the following definition for your member_type
type :
// Class member
template<class type_t, class name_t, type_t default_value = type_t() >
struct member_type {
using type = type_t;
using name = name_t;
type_t value = default_value;
};
You would then define a type which uses template-generated members. Something like this :
template<class ... T>
struct t_templated_members
{
using t_members_list = std::tuple<T...>;
t_members_list members;
};
And you would define the list of members in the a way similar to the one you suggest, but replacing the member's names with types.
// "names" of your members
struct member_x {};
struct member_y {};
using foo = t_templated_members<
member_type<int, member_x, 10>,
member_type<char, member_y, 'a'> >;
With a few of helper templates, you can obtain the value of a member based on it's "name" type.
namespace details
{
// Checks if the member at index I is the right one
template<class T, class U, size_t I>
using is_right_member = std::is_same<
typename std::tuple_element_t<I, typename U::t_members_list>::name, T>;
// Get the index of a member
template<class T, class U, size_t I = 0 >
struct find_element : public std::conditional_t<
is_right_member<T, U, I>::value,
std::integral_constant<decltype(I), I>,
find_element<T, U, I + 1>>
{ };
}
template<class T, class U>
auto & member_get(U & host)
{
constexpr auto index = details::find_element<T, U>::value;
return std::get<index>(host.members).value;
};
Using member_get
you can now access the members you defined for foo
:
#include <iostream>
int main()
{
foo my_foo;
auto & x = member_get<member_x>(my_foo);
std::cout << x << ' ';
x = 6;
std::cout << member_get<member_x>(my_foo) << '\n';
std::cout << member_get<member_y>(my_foo) << ' ';
member_get<member_y>(my_foo) = 'b';
std::cout << member_get<member_y>(my_foo) << '\n';
return 0;
}
C++ does not have reflection tools (yet). In particular, it is impossible to generate and manipulate the name of entities the way you wish.
The preprocessor, however, can do that in a limited way, which (with the help of Boost.PP for the boilerplate that makes it tick) enables us to write the following (directly taken from another answer of mine):
#define GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_0(...) \
((__VA_ARGS__)) GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_1
#define GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_1(...) \
((__VA_ARGS__)) GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_0
#define GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_0_END
#define GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_1_END
// Double the parentheses of a Boost.PP sequence
// I.e. (a, b)(c, d) becomes ((a, b))((c, d))
#define GLK_PP_SEQ_DOUBLE_PARENS(seq) \
BOOST_PP_CAT(GLK_PP_DETAIL_SEQ_DOUBLE_PARENS_0 seq, _END)
#define MAKE_ONE_VARIABLE(r, data, elem) \
BOOST_PP_TUPLE_ELEM(0, elem) BOOST_PP_TUPLE_ELEM(1, elem) = BOOST_PP_TUPLE_ELEM(2, elem);
#define MAKE_CLASS(className, members) \
struct className { \
BOOST_PP_SEQ_FOR_EACH(MAKE_ONE_VARIABLE, ~, GLK_PP_SEQ_DOUBLE_PARENS(members)) \
}
... and use it as such:
MAKE_CLASS(Baz, (int, foo, 42)(float, bar, 0.1f));
... which expands to:
struct Baz {
int foo = 42;
float bar = 0.1f;
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With