I'm attempting to generate some code with preprocessor.
In some class, I need to define multiple variable member, their corresponding setters and a map containing reference on each declared variable.
To illustrate my need, you can find the following code example of what I want to achieve.
In this example, I only declare two variable but in the real case more variable should be declared in different class:
#include <boost/preprocessor.hpp>
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
class typeBase {
};
template <typename T>
class typeBase_: public typeBase {
private:
T value_;
public:
typeBase_() { }
typeBase_(const T& v): value_(v) { }
};
using typeInt = typeBase_<int>;
using typeStr = typeBase_<std::string>;
std::unordered_set<std::string> changed_list_;
//////////////////////////////////////////////////////////
// Here use generation to generate the bellow code
//////////////////////////////////////////////////////////
typeInt mInt_;
typeStr mStr_;
std::unordered_map<std::string, typeBase&> properties_ = {
{"mInt", mInt_},
{"mStr", mStr_}
};
void set_mInt(const typeInt& mInt) {
mInt_ = mInt;
changed_list_.insert("mInt");
}
void set_mStr(const typeStr& mStr) {
mStr_ = mStr;
changed_list_.insert("mStr");
}
/////////////////////////////////////////////
/////////////////////////////////////////////
int main()
{
set_mInt(2);
set_mStr(std::string("test"));
}
At the time I first tried with Boost preprocessing library and I am stuck for the moment at creating the map containing reference to each variable member:
#define MODEL_DECLARE(...) \
std::unordered_map<std::string, typeBase&> properties = { \
MODEL_GENERATE_MAP_ITEMS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
};
#define MODEL_GENERATE_MAP_ITEMS(Args) \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MODEL_GENERATE_MAP_ITEM, %%, Args))
#define MODEL_GENERATE_MAP_ITEM(s, Unused, Arg) \
{(MODEL_STRINGIFY(BOOST_PP_TUPLE_ELEM(2, 0, Arg)), BOOST_PP_TUPLE_ELEM(2, 0, Arg))}
#define MODEL_STRINGIFY_(V) #V
#define MODEL_STRINGIFY(V) MODEL_STRINGIFY_(V)
#define MODEL_MAKE_ITEM(s, Unused, Arg) \
{BOOST_PP_TUPLE_ELEM(2, 0, Arg) BOOST_PP_TUPLE_ELEM(2, 1, Arg)}
// Generate model with this line
MODEL_DECLARE((mInt, typeInt), (mStr, typeStr))
With this code I produce this preprocessing line:
std::unordered_map<std::string, typeBase&> properties = { {("mInt", mInt)}, {("mStr", mStr)} };
As you can see, I have parenthesis which need to be removed, which I barrely failed to to.
Do you know a better solution to achieve what I need, or how can I FIX my code to successfully generate the needed code?
Regards
EDIT1:
I started to implement the #parktomatomi solution and I also tryed to add code to declare variable and setters:
#include <boost/preprocessor.hpp>
#include <iostream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <cassert>
class typeBase {
};
template <typename T>
class typeBase_: public typeBase {
private:
T value_;
public:
typeBase_() { }
typeBase_(const T& v): value_(v) { }
};
using typeInt = typeBase_<int>;
using typeStr = typeBase_<std::string>;
std::unordered_set<std::string> changed_list_;
//////////////////////////////////////////////////////////
// Here use generation to generate the bellow code
//////////////////////////////////////////////////////////
template <typename... Ts>
std::unordered_map<std::string, typeBase*> build_properties(Ts&&... args) {
return std::unordered_map<std::string, typeBase*> { { args.first, args.second }... };
}
// Macro used to generate properties map
#define MODEL_GENERATE_MAP_ITEM(Name, Type) std::make_pair( #Name, &Name##_ )
#define MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg
#define MODEL_GENERATE_MAP_ITEMS(Args) BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MODEL_UNWRAP_MAP_ITEM,,Args))
// Macro used to declare vars and setters
#define MODEL_GENERATE_VAR(Name, Type) Type Name##_; \
void set_##Name(const Type& Name) { \
Name##_ = Name; \
changed_list_.insert(#Name); \
};
#define MODEL_UNWRAP_VAR(Unused1, Unused2, Arg) MODEL_GENERATE_VAR Arg
#define MODEL_GENERATE_VARS(Args) BOOST_PP_SEQ_TRANSFORM(MODEL_UNWRAP_VAR,,Args)
// Macro to generate model
#define MODEL_DECLARE(...) \
std::unordered_map<std::string, typeBase*> properties_ = build_properties( \
MODEL_GENERATE_MAP_ITEMS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
); \
MODEL_GENERATE_VARS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__))
// Generate model
MODEL_DECLARE((mInt, typeInt), (mStr, typeStr))
int main() {
assert(properties_.size() == 2);
assert(properties_["mInt"] == &mInt_);
assert(properties_["mStr"] == &mStr_);
}
However this do not compile because the preprocessing generation add parenthesis around the declaration:
(typeInt mInt_; void set_mInt(const typeInt& mInt) { mInt_ = mInt; changed_list_.insert("mInt"); };) (typeStr mStr_; void set_mStr(const typeStr& mStr) { mStr_ = mStr; changed_list_.insert("mStr"); };)
How to remove this paranthesis?
I ran into two issues trying to get this to compile:
You can't have a container of references
Macros and braces don't mix well
To fix #1, use a pointer instead:
std::unordered_map<std::string, typeBase*>
To fix #2, use a helper function to initialize the map:
template <typename... Ts>
std::unordered_map<std::string, typeBase*> build_properties(Ts&&... args) {
return std::unordered_map<std::string, typeBase*> { { args.first, args.second }... };
}
Then the goal becomes to generate this with macros:
build_properties(
std::make_pair("mInt", &mInt_),
std::make_pair("mStr", &mStr_)
)
Which is a bit easier and compiles successfully:
#define MODEL_GENERATE_MAP_ITEM(Name, Type) std::make_pair( #Name, &Name##_ )
#define MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg
#define MODEL_GENERATE_MAP_ITEMS(Args) \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MODEL_UNWRAP_MAP_ITEM,,Args))
#define MODEL_DECLARE(...) \
std::unordered_map<std::string, typeBase*> properties = build_properties( \
MODEL_GENERATE_MAP_ITEMS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
);
// Generate model with this line
MODEL_DECLARE((mInt, typeInt), (mStr, typeStr))
More directly related to your question, the BOOST_PP_VARIADIC_TO_SEQ macro adds a pair of parenthesis around your arguments:
(mInt, typeInt), (mStr, typeStr) ==> ((mInt, typeInt)) ((mStr, typeStr))
So when BOOST_PP_SEQ_TRANSFORM generates its macros, the argument generated for each transformation macro has parenthesis around it, e.g. (mInt, typeInt). To get rid of those parenthesis, I added this macro:
MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg
which when Arg is replaced:
MODEL_GENERATE_MAP_ITEM (mInt, typeInt)
which gets transformed one last time to:
std::make_pair( "mInt", mInt_ )
Demo: https://godbolt.org/z/5fyo3N
Update
To get the updated code working, I had to make 3 changes:
Instead of BOOST_PP_SEQ_TRANSFORM, use BOOST_PP_SEQ_FOR_EACH. The former does a map on each item and turns it back into a SEQ:
BOOST_PP_SEQ_TRANSFORM(ADD_1, , (a)(b)) ==> (a+1)(b+1)
While BOOST_PP_SEQ_FOR_EACH does a mapping operation, but does not add the parenthesis back:
BOOST_PP_SEQ_TRANSFORM(ADD_1, , (a)(b)) ==> a+1 b+1
Since you've got that set_mInt function, you've got to add an assignment operator to typeBase_<T>:
typeBase_& operator =(const typeBase_& that) { value_ = that.value_; return *this; }
MODEL_GENERATE_VARS needs to be before the map declaration so mInt_ and friends are visible
All together:
#define MODEL_GENERATE_MAP_ITEM(Name, Type) std::make_pair( #Name, &Name##_ )
#define MODEL_UNWRAP_MAP_ITEM(Unused1, Unused2, Arg) MODEL_GENERATE_MAP_ITEM Arg
#define MODEL_GENERATE_MAP_ITEMS(Args) BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(MODEL_UNWRAP_MAP_ITEM,,Args))
// Macro used to declare vars and setters
#define MODEL_GENERATE_VAR(Name, Type) Type Name##_; \
void set_##Name(const Type& Name) { \
Name##_ = Name; \
changed_list_.insert(#Name); \
};
#define MODEL_UNWRAP_VAR(Unused1, Unused2, Arg) MODEL_GENERATE_VAR Arg
#define MODEL_GENERATE_VARS(Args) BOOST_PP_SEQ_FOR_EACH(MODEL_UNWRAP_VAR,,Args)
// Macro to generate model
#define MODEL_DECLARE(...) \
MODEL_GENERATE_VARS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
std::unordered_map<std::string, typeBase*> properties_ = build_properties( \
MODEL_GENERATE_MAP_ITEMS(BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) \
);
// Generate model
MODEL_DECLARE((mInt, typeInt), (mStr, typeStr))
Demo: https://godbolt.org/z/bDDe94
Related
I have some well-formed code looks like this:
NAMESPACE_BEGIN(Foo)
inline void test() {
string s = xxx;
}
NAMESPACE_END(Foo)
So, is there any portable tricks by using the NAMESPACE_BEGIN() macro to obtain namespace name "Foo" in test()?
I'm thinking of something like this, but it would surely cause symbol redefinition:
#define NAMESPACE_BEGIN(x) \
namespace x { \
inline const char *_curNamespace() { \
return #x; \
}
#define NAMESPACE_END(x) \
}
There's also a workaround looks like this, but that's not very convenient
#define NAMESPACE_NAME Foo
// using a header file so that we can use #ifdef guard
#include "MyNamespaceBegin.h"
...
#include "MyNamespaceEnd.h"
EDIT:
Why I need this:
I'm using much of macro to generate codes to achieve some
dynamic reflection logic (yes, not static template reflection),
it's all right within class scope by using static member function,
but does not work for namespaces
Why not to manually declare the name getter once:
What I want is something like this:
// the global default version
const char *_curNamespace() {return "";}
namespace X {
// the local namespace version
const char *_curNamespace() {return "X";}
// some verbose reflection register code
...
registerSomething(_curNamespace());
...
}
Of course, all of the verbose register code should be generated by macro
And, app level user should not care about the _curNamespace(),
so, I want to simplify the user's usage,
by using a custom NAMESPACE_BEGIN(xxx) macro at any case
If you are still curious about what I'm doing,
check this: https://github.com/ZFFramework/ZFFramework
I'm using lots of tricks to achieve fully dynamic reflection in pure C++,
to achieve some of my fancy thoughts,
for now, this project is just for fun,
I have no idea whether it has practicability
EDIT2:
For now, I think the best workaround should be like this:
#define NAMESPACE_BEGIN(ns) \
namespace ns { \
extern const char *__curNS();
#define NAMESPACE_END(ns) \
}
#define NAMESPACE_REG(ns) \
const char *__curNS() {return #ns;}
app level users still only need to care about NAMESPACE_BEGIN
NAMESPACE_REG must be declared exactly once, in source file
if not, undefined symbol would happen
if more than once, duplicated symbol would happen
although it's annoying and sometimes you need additional source file
to hold the NAMESPACE_REG,
the strict rule should prevent user from forgetting the ugly workaround
You are making much fuss over something that is trivial to implement.
First of all, use of NAMESPACE_BEGIN and NAMESPACE_END seems unnecessary to me. I don't see how that is more readable or useful than
namespace Foo
{
}
If getting the name of the namespace is important/useful, add a trivial function.
namespace Foo
{
inline std::string get_name() { return "Foo"; }
}
Small sized real world applications need thousands of lines of code. Large sized real world applications need millions of lines of code. From that perspective, implementing a one line inline function is a very minor task.
This solution employs a bit of preprocessor magic and has these features:
Namespace is mentioned only once
Access to a macro containing the unquoted name
Access to a macro containing the quoted name
Support for repeating the same namespace
Support for different namespaces
Misuse of the BEGIN/END macros is detected
Cleanup, i.e. no extra macros defined outside the BEGIN/END block
It does not support nested namespaces.
Example of usage:
#include "framework.hpp"
#define NAMESPACE_NAME Foo
#include NAMESPACE_BEGIN
// Here you have access to NAMESPACE_NAME (unquoted, i.e. Foo)
// and also to NAMESPACE_NAME_STRING (quoted, i.e. "Foo")
#include NAMESPACE_END
// NAMESPACE_NAME and NAMESPACE_NAME_STRING do not exist
// outside the block, so they cannot be misused
// Different namespaces in the same TU are supported
#define NAMESPACE_NAME Bar
#include NAMESPACE_BEGIN
inline std::string f()
{
return NAMESPACE_NAME_STRING;
}
#include NAMESPACE_END
// Repeating the same namespace is also supported
#define NAMESPACE_NAME Foo
#include NAMESPACE_BEGIN
inline std::string f()
{
return NAMESPACE_NAME_STRING;
}
#include NAMESPACE_END
The implementation follows:
framework.hpp
#pragma once
#define NAMESPACE_BEGIN "framework_namespace_begin.hpp"
#define NAMESPACE_END "framework_namespace_end.hpp"
framework_namespace_begin.hpp
#ifndef NAMESPACE_NAME
#error "NAMESPACE_NAME not defined"
#endif
#define NAMESPACE_IN_NAMESPACE 1
#define NAMESPACE_NAME_DO_STR(X) #X
#define NAMESPACE_NAME_STR(X) NAMESPACE_NAME_DO_STR(X)
#define NAMESPACE_NAME_STRING NAMESPACE_NAME_STR(NAMESPACE_NAME)
namespace NAMESPACE_NAME {
framework_namespace_end.hpp
#ifndef NAMESPACE_IN_NAMESPACE
#error "NAMESPACE_IN_NAMESPACE not defined"
#endif
}
#undef NAMESPACE_NAME
#undef NAMESPACE_NAME_STRING
#undef NAMESPACE_IN_NAMESPACE
You know what? I think I might just have a viable solution for this. It's actually very simple, and it's very close to the OP's original suggestion (which really only had the problem of a potential duplicate definition if you wanted to open the namespace twice in the same translation unit). You just have to think a bit laterally and not be too precious about seeing your namespaces being bracketed by macros instead of curly braces.
So let me just lay it out here, because there's really nothing to it, and then I'll explain why I personally happen to like it.
Code:
Macros:
#define DECLARE_NAMESPACE(ns) \
namespace ns {\
static constexpr const char *_curNamespace = #ns; \
}
#define BEGIN_NAMESPACE(ns) \
namespace ns { \
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
#define END_NAMESPACE }
Sample code:
#include <iostream>
DECLARE_NAMESPACE (Foo)
BEGIN_NAMESPACE (Foo)
void print_namespace_name () { std::cout << _curNamespace << "\n"; }
END_NAMESPACE
BEGIN_NAMESPACE (Foo)
void another_print_namespace_name () { std::cout << _curNamespace << "\n"; }
END_NAMESPACE
DECLARE_NAMESPACE (Bar)
BEGIN_NAMESPACE (Bar)
void print_namespace_name () { std::cout << _curNamespace << "\n"; }
DECLARE_NAMESPACE (BarBar)
BEGIN_NAMESPACE (BarBar)
void print_namespace_name () { std::cout << _curNamespace << "\n"; }
END_NAMESPACE
END_NAMESPACE
int main ()
{
Foo::print_namespace_name ();
Foo::another_print_namespace_name ();
Bar::print_namespace_name ();
Bar::BarBar::print_namespace_name ();
}
Output:
Foo
Foo
Bar
BarBar
Now this is obviously very straightforward to implement and also easy to use and has no obvious limitations. In particular, it can handle nested namespaces (as shown in the code above) and opening a namespace twice in the same compilation unit also works (again, this is shown in the code snippet).
But, but, but, don't we still have to type in the name of the namespace twice, and wasn't that the very thing we were trying to avoid to eliminate typos?
Well, sure, we have to type the name in twice, but so what, live with it. Point is, with this particular set of macros, the compiler will now catch any typos for us. Let's prove that by deliberately putting one in. So this:
DECLARE_NAMESPACE Whoops
BEGIN_NAMESPACE whoops
END_NAMESPACE
Generates this (I couldn't find a better way of formulating the static_assert, sorry):
prog.cc:12:24: error: '_curNamespace' is not a member of 'whoops'
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
^~~~~~~~~~~~~
prog.cc:27:5: note: in expansion of macro 'BEGIN_NAMESPACE'
BEGIN_NAMESPACE (whoops)
^~~~~~~~~~~~~~~
And more importantly this (and this is why we need the BEGIN_NAMESPACE macro):
DECLARE_NAMESPACE (Bar)
BEGIN_NAMESPACE (Bar)
DECLARE_NAMESPACE (BarWhoops)
BEGIN_NAMESPACE (Barwhoops)
END_NAMESPACE
END_NAMESPACE
Generates this:
prog.cc:12:24: error: '_curNamespace' is not a member of 'Bar::Barwhoops'
static_assert (ns::_curNamespace, "BEGIN_NAMESPACE: namespace has not been declared");
^~~~~~~~~~~~~
prog.cc:42:5: note: in expansion of macro 'BEGIN_NAMESPACE'
BEGIN_NAMESPACE (Barwhoops)
^~~~~~~~~~~~~~~
Which is just dandy.
So, you know, what's not to like?
Live demo - uncomment line 3 to see those compiler errors.
you can use a variable and change its value with 'NAMESPACE_BEGIN' and 'NAMESPACE_END'
the variable __name represent the current full namespace position
like "abc::def::detail"
std::string __name = "";
std::string & __append(std::string & str, const char * ptr) {
if (!str.empty()) {
str.append("::");
}
str.append(ptr);
return str;
}
std::string & __substr(std::string & str, const char * ptr) {
if (str.length() == strlen(ptr)) {
str.clear();
} else {
str = str.substr(0, str.length() - strlen(ptr) - 2);
}
return str;
}
#define NAMESPACE_NAME __name
#define CONCATENATE_DIRECT(s1, s2) s1##s2
#define CONCATENATE(s1, s2) CONCATENATE_DIRECT(s1, s2)
#ifdef _MSC_VER
# define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __COUNTER__)
#else
# define ANONYMOUS_VARIABLE(str) CONCATENATE(str, __LINE__)
#endif
#define APPEND_NAME(x) std::string ANONYMOUS_VARIABLE(__start_name) = __append(__name, #x)
#define SUBSTR_NAME(x) std::string ANONYMOUS_VARIABLE(__end_name) = __substr(__name, #x)
#define NAMESPACE_BEGIN(x) \
namespace x { \
APPEND_NAME(x);
#define NAMESPACE_END(x) \
SUBSTR_NAME(x); \
}
then you can use the NAMESPACE_NAME macro for the full name or you can extract the last name from it
Here's a way. The core idea came from the line of thought:
Q: How can I define multiple things with the same name accessible from the same scope?
A: Make them all functions with different parameter types. And if all of them have identical bodies, it doesn't matter which one gets called.
Q: How can I generate an unlimited set of different parameter types?
A: A class template.
Q: How can I make sure a call to that set of overloaded functions will never be ambiguous?
A: Make sure the binary relation "is implicitly convertible from" is a complete ordering on the parameter types, and use a unique minimal element for the argument type.
#include <type_traits>
#include <functional>
struct NamespaceHandleObj {
template <const NamespaceHandleObj* Handle1, const NamespaceHandleObj* Handle2>
struct less : public std::bool_constant<std::less<>{}(Handle1, Handle2)> {};
};
template <>
struct NamespaceHandleObj::less<nullptr, nullptr> : public std::false_type {};
template <const NamespaceHandleObj* Handle1>
struct NamespaceHandleObj::less<Handle1, nullptr> : public std::false_type {};
template <const NamespaceHandleObj* Handle2>
struct NamespaceHandleObj::less<nullptr, Handle2> : public std::true_type {};
template <const NamespaceHandleObj* Handle>
struct NamespaceParamType
{
constexpr NamespaceParamType() noexcept = default;
template <const NamespaceHandleObj* Other,
typename = std::enable_if_t<NamespaceHandleObj::less<Other, Handle>::value>>
constexpr NamespaceParamType(NamespaceParamType<Other>) noexcept {}
};
#define NAMESPACE_UTILS_TOSTR1(x) #x
#define NAMESPACE_UTILS_TOSTR(x) NAMESPACE_UTILS_TOSTR1(x)
#define BEGIN_NAMESPACE(ns) \
namespace ns { \
namespace { \
constexpr NamespaceHandleObj namespace_handle_{}; \
constexpr const char* current_ns_(
NamespaceParamType<&namespace_handle_>) noexcept \
{ return NAMESPACE_UTILS_TOSTR(ns); } \
}
#define END_NAMESPACE }
#define CURRENT_NAMESPACE (current_ns_(NamespaceParamType<nullptr>{}))
The code above is C++17, but it wouldn't be hard to port it to previous versions, even all the way to C++03.
I recently started using tuples instead of plain class members because I find it convenient to work with them. So my code looks something like this:
class TestClass final {
public:
TestClass() = default;
~TestClass() = default;
public:
template<int M>
auto get()->decltype(std::get<M>(m_private_members)) const {
return std::get<M>(m_private_members);
}
enum PrivateIdx {
count,
use_stuff,
name
};
private:
std::tuple<int, bool, std::string> m_private_members{1, true, "bla"};
};
So this can be used now like:
std::cout << t.get<TestClass::name>()> << std::endl;
This work also fine - the only thing is, adding members can be quite error-prone. One can easily get the access enums wrong by mixing up the order or forgetting a member. I was thinking of a macro style thing like:
PUBLIC_MEMBERS(
MEMBER(int count),
MEMBER(std::string name)
);
This would expand to the tuple and enum code. Problem is, I don't think this can be solved by a macro because it's two different data structures it would have to expand to, right? Also, I must admit, I've never looked into complicated macros.
I was also thinking of a template for solving this, but I could also not come up with a viable solution because enums cannot be generated by a template.
Interesting problem. I'm curious why you'd like to do this. This is something I came up with. Good news: no macros!
The main problem, I think, is that you want to declare identifiers to access members. This cannot be solved with templates, so you have to either a) use macros, or b) somehow declare those identifiers without directly. Instead of using constants/enumerations, I tried to use type names to identify member in get.
I'll start with an example of use:
class User
{
public:
enum class AccessLevel
{
ReadOnly,
ReadWrite,
Admin
};
struct Name : MemberId<std::string> {};
struct Id : MemberId<unsigned> {};
struct Access : MemberId<AccessLevel> {};
template<typename MemberType>
auto& get() { return PrivMembers::getFromTuple<MemberType>(m_members); }
template<typename MemberType>
const auto& get() const { return PrivMembers::getFromTuple<MemberType>(m_members); }
private:
using PrivMembers = MembersList<Name, Id, Access>;
PrivMembers::Tuple m_members;
};
int main()
{
User user;
user.get<User::Name>() = "John Smith";
user.get<User::Id>() = 1;
user.get<User::Access>() = User::AccessLevel::ReadWrite;
return 0;
}
Name, Id and Access are used for identifying elements of m_members tuple. These structures don't have any members themselves. PrivMembers::Tuple is alias for std::tuple<std::string, unsigned, AccessLevel>:
template<typename Type_>
struct MemberId { using Type = Type_; };
template<typename... Types>
struct MembersList
{
using Tuple = std::tuple<typename Types::Type...>;
template<typename T>
static auto& getFromTuple(Tuple& tp) { return std::get<detail::IndexOf<T, Types...>::value>(tp); }
template<typename T>
static const auto& getFromTuple(const Tuple& tp) { return std::get<detail::IndexOf<T, Types...>::value>(tp); }
};
First thing: Tuple alias. I think its self explanatory what happens. Then, there are to overloads for getFromTuple, which is used by User class.
When using MemberId-derived types instead of constants for accessing elements of the tuple, I need to find index of corresponding to given member Id. That what happens in getFromTuple. There's a helper class which does searching:
namespace detail
{
template<typename Needle, typename HaystackHead, typename... Haystack>
struct IndexOf { static constexpr std::size_t value = IndexOf<Needle, Haystack...>::value + 1; };
template<typename Needle, typename... Haystack>
struct IndexOf<Needle, Needle, Haystack...> { static constexpr std::size_t value = 0; };
}
All of this solves the problem of having to maintain indices for each members, as in your original solution. Syntax for declaring member Ids (struct Name : MemberId<std::string> {};) might be bit annoying, but I cannot think about more compact solution.
All of this works with C++14. If you can live with trailing return type for User::get, then you could compile it as C++11.
Here's full code.
Like I said in comment macros are pain to debug. One who does not see how to write some should think twice if to use these at all. OTOH these are relatively straightforward to write when one gets the logic of those.
Note that given is just one way to do it, like with everything there are several.
So macros are like that:
#define GET_NAME(NAME,TYPE,VALUE) NAME
#define GET_TYPE(NAME,TYPE,VALUE) TYPE
#define GET_VALUE(NAME,TYPE,VALUE) VALUE
#define DECLARE_ENUM(PRIVATES) \
enum PrivateIdx { \
PRIVATES(GET_NAME) \
};
#define DECLARE_TUPLE(PRIVATES) \
std::tuple<PRIVATES(GET_TYPE)> m_private_members{PRIVATES(GET_VALUE)};
#define DECLARE_IN_ONE_GO(PRIVATES) \
public: \
DECLARE_ENUM(PRIVATES) \
private: \
DECLARE_TUPLE(PRIVATES)
And usage is like that:
#include <iostream>
#include <tuple>
#include "enum_tuple_macros.h"
class TestClass final {
public:
TestClass() = default;
~TestClass() = default;
#define PRIVATES(MEMBER) \
MEMBER(count,int,1), \
MEMBER(use_stuff,bool,true), \
MEMBER(name,std::string,"bla")
DECLARE_IN_ONE_GO(PRIVATES)
// note that the get can be also generated by DECLARE_IN_ONE_GO
public:
template<int M>
auto get() const -> decltype(std::get<M>(m_private_members)) {
return std::get<M>(m_private_members);
}
};
int main()
{
TestClass t;
std::cout << t.get<TestClass::name>() << " in one go" << std::endl;
}
Seems to work on gcc 8.1.0 i tried.
In the meantime I came up with something using var args...
taken from
[https://stackoverflow.com/questions/16374776/macro-overloading][1]
#define EXPAND(X) X
#define __NARG__(...) EXPAND(__NARG_I_(__VA_ARGS__,__RSEQ_N()))
#define __NARG_I_(...) EXPAND(__ARG_N(__VA_ARGS__))
#define __ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N
#define __RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0
// general definition for any function name
#define _VFUNC_(name, n) name##n
#define _VFUNC(name, n) _VFUNC_(name, n)
#define VFUNC(func, ...) EXPAND(_VFUNC(func, EXPAND( __NARG__(__VA_ARGS__))) (__VA_ARGS__))
#define MEMBER_LIST(...) EXPAND(VFUNC(MEMBER_LIST, __VA_ARGS__))
#define MEMBER_LIST3(mem_type1, mem_name1, default_value1)\
\
enum PrivateIdx { \
mem_name1 \
}; \
\
std::tuple<mem_type1> m_private_members{default_value1}
#define MEMBER_LIST6( mem_type0, mem_name0, default_value0,\
mem_type1, mem_name1, default_value1)\
\
enum PrivateIdx { \
mem_name0, \
mem_name1 \
}; \
\
std::tuple< mem_type0, \
mem_type1 > m_private_members{ default_value0, \
default_value1}
..and so on
Works, but imho still not elegant enough. I think I got pointed in the right direction.
Let's say I have some unspecified type called variant, as well as two functions allowing to convert to/from this type, with the following signature:
struct converter
{
template<typename T>
static variant to(const T&);
template<typename T>
static T from(const variant&);
};
Now, what I'd like to do is create wrappers for arbitrary C++ functions as in the following example:
SomeObject f_unwrapped(const std::string& s, int* x)
{
//... do something with the inputs...
return SomeObject();
}
extern "C" variant f(variant s, variant x)
{
return converter::to<SomeObject>(f_unwrapped(converter::from<std::string>(s), converter::from<int*>(x)));
}
Ideally I'd want the wrapper to be a one-line declaration or macro that would take only the f_unwrapped function and the name f as inputs.
I've tried to wrap the function into a function object, then do the bureaucratic work using variadic templates. While this does work, I don't know how to make the resulting function extern "C".
What is the most idiomatic way of achieving this goal?
If we use the EVAL, helper, Conditional, and map macros from the first two code blocks here.
The map will need to be made more general for our needs.
#define MM1() MM_CALL1
#define MM_NEXT1(Macro,a,...) \
IS_DONE(a)( \
EAT \
, \
OBSTRUCT(COMMA)() OBSTRUCT(MM1)() \
) \
(Macro,a,__VA_ARGS__)
#define MM_CALL1(Macro,a,...) \
Macro(a) \
MM_NEXT1(Macro,__VA_ARGS__)
#define MacroMap1(Macro,...) MM_CALL1(Macro,__VA_ARGS__,DONE)
#define MM2() MM_CALL2
#define MM_NEXT2(Macro,a,...) \
IS_DONE(a)( \
EAT \
, \
OBSTRUCT(COMMA)() OBSTRUCT(MM2)() \
) \
(Macro,a,__VA_ARGS__)
#define MM_CALL2(Macro,a,b,...) \
Macro(a,b) \
MM_NEXT2(Macro,__VA_ARGS__)
#define MacroMap2(Macro,...) MM_CALL2(Macro,__VA_ARGS__,DONE)
We will also want the WithTypes and WithoutTypes from here.
We can define AMACRO to do the job you wanted.
#define AsVariant(param) variant param
#define ConvertFrom(type,param) converter::from<type>(param)
#define HEADDER(type,func,params) type func ##_unwrapped (WithTypes params)
#define WRAPPER(type,func,params) \
extern "C" variant func (OBSTRUCT(MacroMap1)(AsVariant,WithoutTypes params)) \
{ \
return converter::to< type >(func ## _unwrapped( \
MacroMap2(ConvertFrom,IDENT params) \
)); \
}
#define AMACRO(type,func,params) \
EVAL( \
HEADDER(type,func,params); \
WRAPPER(type,func,params) \
HEADDER(type,func,params) \
)
Which will turn this:
AMACRO(SomeObject,f,(const std::string&, s, int*, x))
{
// ... do something with the inputs ...
return SomeObject();
}
Into this (after formatting):
SomeObject f_unwrapped (const std::string& s , int* x );
extern "C" variant f (variant s , variant x )
{
return converter::to<SomeObject>(f_unwrapped(converter::from<const std::string&>(s),converter::from<int*>(x)));
}
SomeObject f_unwrapped (const std::string& s , int* x )
{
return SomeObject();
}
NOTE:
If the const needs removing from the parameter, a conditional, similar to the ISDONE, can be made and added to the ConvertFrom macro.
What's the best way to deal with extra trailing commas when working with X macros? Specifically, I have the following setup in a file test01.cpp
struct Foo {
#define X(name,val) int name;
#include "test01.def"
#undef X
Foo() :
#define X(name,val) name(val),
#include "test01.def"
#undef X
{}
};
int main(){
Foo foo;
}
In test01.def, I have
X(foo,1)
X(bar,23)
This doesn't compile because of the error
test01.cpp: In constructor 'Foo::Foo()':
test01.cpp:10:5: error: expected identifier before '{' token
{}
Basically, there's a trailing comma after the last element in the member initializer list. Now, we can fix this by adding a dummy variable:
struct Foo {
private:
void * end;
public:
#define X(name,val) int name;
#include "test01.def"
#undef X
Foo() :
#define X(name,val) name(val),
#include "test01.def"
#undef X
end(nullptr)
{}
};
int main(){
Foo foo;
}
However, this is sort of ugly. As such, is there a better way to handle the trailing comma in the member initializer list?
Edit 1
Here's another option that's still kind of ugly:
struct Foo {
#define X(name,val) int name;
#include "test01.def"
#undef X
Foo() :
#define X(name,val) name(val),
#define XLAST(name,val) name(val)
#include "test01.def"
#undef XLAST
#undef X
{}
};
int main(){
Foo foo;
}
along with
#ifndef XLAST
#define XLAST X
#define CLEANUP
#endif
X(foo,1)
XLAST(bar,23)
#ifdef CLEANUP
#undef XLAST
#undef CLEANUP
#endif
Basically, we define the macro XLAST to take care of the final comma. If we use XLAST, we have to manually undefine it like X, but we do this automatically in the case that we don't define it explicitely.
Since you've tagged this C++14, the easiest way to solve this problem is to use brace-or-equal-initializers instead of the mem-initializers.
struct Foo {
#define X(name,val) int name = val;
#include "test01.def"
#undef X
// Foo() {} // uncomment if you do not want Foo to be an aggregate
};
If you want to stick with a preprocessor solution, you can use Boost.Preprocessor to do this. You'll need to change the format of your data member definitions so it forms a sequence.
#define FOO_MEMBERS ((int,i,10)) ((long,j,20))
I added the ability to specify arbitrary data types too.
First let's declare and initialize these data members
struct Foo
{
#define OP(s, data, elem) BOOST_PP_TUPLE_ELEM(3, 0, elem) \
BOOST_PP_TUPLE_ELEM(3, 1, elem) = \
BOOST_PP_TUPLE_ELEM(3, 2, elem);
BOOST_PP_SEQ_FOR_EACH(OP, , FOO_MEMBERS)
// expands to
// int i = 10; long j = 20;
#undef OP
Foo() = default; // default constructor
};
BOOST_PP_SEQ_FOR_EACH will expand the macro OP for each element in the sequence FOO_MEMBERS.
BOOST_PP_TUPLE_ELEM simply extracts a single element from its tuple argument.
Next, let's give Foo a constructor that takes arguments corresponding to each data member and initializes it.
#define OP(s, data, elem) (BOOST_PP_TUPLE_ELEM(3, 0, elem) BOOST_PP_TUPLE_ELEM(3, 1, elem))
Foo(
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH(OP, , FOO_MEMBERS))
// expands to
// int i, long j
) :
#undef OP
#define OP(s, data, elem) (BOOST_PP_TUPLE_ELEM(3, 1, elem)(BOOST_PP_TUPLE_ELEM(3, 1, elem)))
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH(OP, , FOO_MEMBERS))
// expands to
// i(i), j(j)
#undef OP
{}
We use BOOST_PP_SEQ_ENUM to generate a comma separated list from the result of the expansion of BOOST_PP_SEQ_FOR_EACH.
Live demo
If you declare end to be of std::nullptr_t type the compiler is likely to optimize it and remove it. And the intent is clear to the reader.
struct Foo {
private:
static std::nullptr_t end;
alternatively, you might declare char end[0]; (this won't use any space) but some compilers might reject that.
Of course, as Pratorian answered, you could use some X-macro friendly construct instead.
For anybody looking for a solution not involving a dependency on BOOST_PP, a preprocessor-based alternative, also useful for plain-old C, is to deal with a leading comma using the preprocessor, which is much easier than dealing with a trailing comma.
/// Indirection macro, so that macro expansion of parameters is done before token pasting.
#define REMOVE_FIRST(...) REMOVE_FIRST_SUB(__VA_ARGS__)
/// Paste all parameters but first.
#define REMOVE_FIRST_SUB(X, ...) __VA_ARGS__
#define FOO_DEF(X) \
X(foo,1) \
X(bar,23)
struct Foo {
#define X(name,val) int name;
FOO_DEF(X)
#undef X
Foo() :
#define X(name,val) ,name(val)
REMOVE_FIRST(FOO_DEF(X))
#undef X
{}
};
int main(){
Foo foo;
}
Testing:
$ gcc -E test.c
# 0 "test.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "test.c"
# 11 "test.c"
struct Foo {
int foo; int bar;
Foo() :
foo(1) ,bar(23)
{}
};
int main(){
Foo foo;
}
So, I implemented an enumToString function for several enums that I use a lot (often asked in SO: Is there a simple way to convert C++ enum to string?, Easy way to use variables of enum types as string in C?, ...).
This makes the error messages WAY easier to debug, but I have to maintain the function to add the values that have no string description sometimes.
My code looks like this:
typedef std::map<my_enum_e, const char *> enum_map_t;
static bool s_enum_map_initialized = false;
static enum_map_t s_enum_strings;
static void s_init_maps()
{
#define ADD_ENUM( X ) s_enum_strings[X] = #X;
if( s_enum_strings.size() == 0)
{
ADD_CLASS( MY_ENUM_1 );
ADD_CLASS( MY_ENUM_2 );
/* ... all enums */
}
s_enum_map_initialized = true;
}
const char *Tools::enumCString( my_enum_e e )
{
if( ! s_enum_map_initialized )
{
s_init_maps();
}
// todo: use the iterator instead of searching twice
if( s_enum_strings.find(e) != s_enum_strings.end() )
{
return s_class_strings[e];
}
return "(unknown enum_e)";
}
Now, what I want, is that when I don't find the enum in the map, to return "(unknown enum %d)", e . Which will give me the value of the enum I missed.
This way, even if I didn't add it to the map, I still have its value and I can debug my program.
I can't find a way to do that simply: a stringstream instanciated on the stack will be destroyed right after the return, a static stringstream is not thread-safe, ...
edit: of course, using a std::string as return type would allow me to format it, but I call these functions very often in my code, I figured passing a const char * pointer is faster, since I don't have to push the std::string onto the stack each time.
Any solution?
Return a std::string rather than a char*.
This would allow you to use a std::stringstream to generate your message. The calling site would then just have to use the .c_str( ) member function on std::string to get the C-style pointer (if required).
Personally, I use BOOST :)
Example of use:
SANDBOX_DEFINE_ENUM(MyEnum, (Foo)(Bar)(Team))
Will yield:
struct MyEnum {
enum Type {
Foo,
Bar,
Team
};
static Type const First = Foo;
static Type const Last = Team;
};
inline char const* toString(MyEnum::Type value) {
switch(value) {
case MyEnum::Foo: return "Foo";
case MyEnum::Bar: return "Bar";
case MyEnum::Team: return "Team";
}
return 0;
}
Code:
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/reverse.hpp>
#include <boost/preprocessor/seq/seq.hpp>
#define SANDBOX_DEFINE_ENUM(Name_, Values_) \
SANDBOX_DEFINE_ENUM_TYPE(Name_, Values_) \
SANDBOX_DEFINE_ENUM_STRING(Name_, Values_)
#define SANDBOX_DEFINE_ENUM_TYPE(Name_, Values_) \
struct Name_ { \
enum Type { \
BOOST_PP_SEQ_ENUM(Values_) \
}; \
static Type const First = BOOST_PP_SEQ_HEAD(Values_); \
static Type const Last = BOOST_PP_SEQ_HEAD(BOOST_PP_SEQ_REVERSE(Values_)); \
};
#define SANDBOX_DEFINE_ENUM_STRING(Name_, Values_) \
inline char const* toString(Name_::Type value) { \
switch(value) { \
BOOST_PP_SEQ_FOR_EACH(SANDBOX_DEFINE_ENUM_TO_STRING_C, Name_, Values_) \
} \
return 0; \
}
#define SANDBOX_DEFINE_ENUM_TO_STRING_C(r, Name_, Elem_) \
case Name_::Elem_: return BOOST_PP_STRINGIZE(Elem_);
Obviously, it only work with "regular" enums, not with custom made ones. But because it's defined in a single place in the code... no maintenance penalty :)
Try defining a thread-local static variable http://gcc.gnu.org/onlinedocs/gcc-3.3.1/gcc/Thread-Local.html
I wouldn't return a value in this case. I would throw an exception and have your message contain the value of your invalid enumerator. To me, an invalid enumerator value would seem to be an error.
If you don't want to do that, then I agree with others that you should be returning a std::string instead.