Home > Mobile >  Get type of class within its own templated member method
Get type of class within its own templated member method

Time:10-07

This is a little bit confusing, but I will try to explain the best I can. I have a set of macros that help me to write special member methods which add a wrapper around a member method call.

For example:

class Test {
public:
    DECLARE_METHOD(int, methodName, (int, a) (AMoveOnlyType, b));
};

Which expands to something like the following:

class Test {
public:
    int methodName(int a, AMoveOnlyType b);
    int methodName_WRAPPER(int a, AMoveOnlyType b);
};

Where I have a corresponding macro for the source file as such:

//in .cpp file
DECLARE_METHOD(int, Test::methodName, (int, a) (AMoveOnlyType, b)) {
    //body of the method here
}

Which expands to something like the following:

int Test::methodName(int a, AMoveOnlyType b) {
   //Assume I have a templated function which allows me to queue and call a function at some later point in time
    return execute_later(this, &Test::methodName_WRAPPER, a, b);
}
int Test::methodName_WRAPPER(int a, AMoveOnlyType b) {
    //body of the method here
}

However, I realized I have the need to be able to perfectly forward the arguments passed to methodName on to whatever I do in the wrapper code. The only way to achieve this would be to make methodName a templated function which, in turn, means that it needs to be defined in the header file. So my DECLARE_METHOD macro would expand to something like this:

class Test {
public:
    template <typename ... ARGS>
    inline int methodName(ARGS&&... args) {
        return execute_later(this, &methodName_WRAPPER, std::forward<ARGS>(args)...);
    }
    int methodName_WRAPPER(int a, AMoveOnlyType b);
};

So that I could use it like so:

Test instance;
AMoveOnlyType x;
//I can't write a macro which magically knows if the wrapper needs to move/forward the arguments so this is why I needed to use a template
instance.methodName(1, std::move(x));

The problem is, I get the following error "ISO C forbids taking the address of a bound member function to form a pointer to member function"

I understand that I need to provide the full name of the function just like I did in the DEFINE_METHOD macro. However, since this is in the header file, I would really prefer to use my DECLARE_METHOD macro just as I have been without having to redundantly proivde the name of the class (just like you would when declaring a normal method within a class).

My question is, is there a way to somehow get a pointer to a class' member method within the header without having to provide its full name?

Please let me know if I need to clarify anything.

As a side note, I would really like to make the macro make the methodName_WRAPPER method private (because users of the class should not be allowed to call the wrapper directly) without affecting the scope of following statements, but I don't see a way to do this.

EDIT

As requested in the comments, here are the macros:

/**
 * @brief This macro will remove parenthesis from around its argument only
 *        if they are present.
 *
 * See https://stackoverflow.com/a/62984543/13320909
 */
#define DEPAREN(X) ESC(ISH X)
#define ISH(...) ISH __VA_ARGS__
#define ESC(...) ESC_(__VA_ARGS__)
#define ESC_(...) VAN ## __VA_ARGS__
#define VANISH

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END

#define END_C(...) END_C_(__VA_ARGS__)
#define END_C_(...) __VA_ARGS__##_END_C

//------------------------------------------------------------------------------

#define PARAMS_LOOP(...) END(PARAMS_LOOP_0 __VA_ARGS__)
#define PARAMS_LOOP_C(...) END_C(PARAMS_LOOP_0_C __VA_ARGS__)
#define PARAMS_LOOP_C_POST(...) END_C(PARAMS_LOOP_0 __VA_ARGS__)
#define PARAMS_LOOP_C_PRE(...) END(PARAMS_LOOP_0_C __VA_ARGS__)

#define PARAMS_LOOP_MACRO(_1, _2, _3, NAME, ...) NAME
#define PARAMS_LOOP_BODY(...) PARAMS_LOOP_MACRO(__VA_ARGS__, PARAMS_LOOP_BODY3, PARAMS_LOOP_BODY2)(__VA_ARGS__)

#define PARAMS_LOOP_BODY2(type_, name_, ...) DEPAREN(type_) name_
#define PARAMS_LOOP_BODY3(type_, name_, default_, ...) DEPAREN(type_) name_ = default_

#define PARAMS_LOOP_0(...)     PARAMS_LOOP_BODY(__VA_ARGS__) PARAMS_LOOP_A
#define PARAMS_LOOP_0_C(...) , PARAMS_LOOP_BODY(__VA_ARGS__) PARAMS_LOOP_A
#define PARAMS_LOOP_A(...)   , PARAMS_LOOP_BODY(__VA_ARGS__) PARAMS_LOOP_B
#define PARAMS_LOOP_B(...)   , PARAMS_LOOP_BODY(__VA_ARGS__) PARAMS_LOOP_A
#define PARAMS_LOOP_0_END
#define PARAMS_LOOP_0_END_C
#define PARAMS_LOOP_0_C_END
#define PARAMS_LOOP_0_C_END_C ,
#define PARAMS_LOOP_A_END
#define PARAMS_LOOP_A_END_C ,
#define PARAMS_LOOP_B_END
#define PARAMS_LOOP_B_END_C ,

//------------------------------------------------------------------------------

/**
* Examples 1:
* NTH_LOOP(1, (int, a) (char, b) (uint8_t, c))
* END(NTH_LOOP_0_1 (int, a) (char, b) (uint8_t, c))
* NTH_LOOP_BODY_0(int, a) NTH_LOOP_A_0 (char, b) (uint8_t, c)_END
* DEPAREN(a), NTH_LOOP_BODY_0(char, b) NTH_LOOP_B_0(uint8_t, c)_END
* a, DEPAREN(b), NTH_LOOP_BODY_0(uint8_t, c) NTH_LOOP_A_1_END
* a, b, DEPAREN(c)
* a, b, c
*
* Example 2:
* NTH_LOOP(0)
* END(NTH_LOOP_0_0_END)
*
*/
#define NTH_LOOP(n_, ...)   END(NTH_LOOP_0_ ## n_ __VA_ARGS__)

/**
 * @brief Build a list containing the Nth arguments prefixed and postfixed by a
 *        comma.
 *
 * Example 1:
 * NTH_LOOP_C(0)
 * END(NTH_LOOP_0_0_C_END)
 * ,
 *
 */
#define NTH_LOOP_C(n_, ...)   END_C(NTH_LOOP_0_ ## n_ ## _C __VA_ARGS__)

/**
 * @brief Build a list containing the Nth arguments postfixed by a comma.
 *
 * **Example 1:**\n 
 * NTH_LOOP_C_POST(0)\n 
 * END(NTH_LOOP_0_0_C_END)\n 
 * ,\n 
 * \n 
 * **Example 2:**\n 
 * NTH_LOOP_C_POST(0, (int, a) (char, b))\n 
 * int, char,\n 
 */
#define NTH_LOOP_C_POST(n_, ...)   END_C(NTH_LOOP_0_ ## n_ __VA_ARGS__)

/**
 * @brief Build a list containing the Nth arguments prefixed by a comma.
 *
 * **Example 1:**\n 
 * NTH_LOOP_C_PRE(0)\n 
 * END(NTH_LOOP_0_0_C_END)\n 
 * \n 
 * \n 
 * **Example 2:**\n 
 * NTH_LOOP_C_PRE(0, (int, a) (char, b))\n 
 * , int, char\n 
 */
#define NTH_LOOP_C_PRE(n_, ...)   END(NTH_LOOP_0_ ## n_ ## _C __VA_ARGS__)

#define NTH_LOOP_0_0(...)     NTH_LOOP_BODY_0(__VA_ARGS__) NTH_LOOP_A_0
#define NTH_LOOP_0_0_C(...) , NTH_LOOP_BODY_0(__VA_ARGS__) NTH_LOOP_A_0
#define NTH_LOOP_A_0(...)   , NTH_LOOP_BODY_0(__VA_ARGS__) NTH_LOOP_B_0
#define NTH_LOOP_B_0(...)   , NTH_LOOP_BODY_0(__VA_ARGS__) NTH_LOOP_A_0
#define NTH_LOOP_0_0_END
#define NTH_LOOP_0_0_END_C
#define NTH_LOOP_0_0_C_END
#define NTH_LOOP_0_0_C_END_C ,
#define NTH_LOOP_A_0_END
#define NTH_LOOP_A_0_END_C ,
#define NTH_LOOP_B_0_END
#define NTH_LOOP_B_0_END_C ,
#define NTH_LOOP_BODY_0(arg0_, ...) DEPAREN(arg0_)

#define NTH_LOOP_0_1(...)     NTH_LOOP_BODY_1(__VA_ARGS__) NTH_LOOP_A_1
#define NTH_LOOP_0_1_C(...) , NTH_LOOP_BODY_1(__VA_ARGS__) NTH_LOOP_A_1
#define NTH_LOOP_A_1(...)   , NTH_LOOP_BODY_1(__VA_ARGS__) NTH_LOOP_B_1
#define NTH_LOOP_B_1(...)   , NTH_LOOP_BODY_1(__VA_ARGS__) NTH_LOOP_A_1
#define NTH_LOOP_0_1_END
#define NTH_LOOP_0_1_END_C
#define NTH_LOOP_0_1_C_END
#define NTH_LOOP_0_1_C_END_C ,
#define NTH_LOOP_A_1_END
#define NTH_LOOP_A_1_END_C ,
#define NTH_LOOP_B_1_END
#define NTH_LOOP_B_1_END_C ,
#define NTH_LOOP_BODY_1(arg0_, arg1_, ...) DEPAREN(arg1_)

#define NTH_LOOP_0_2(...)     NTH_LOOP_BODY_2(__VA_ARGS__) NTH_LOOP_A_2
#define NTH_LOOP_0_2_C(...) , NTH_LOOP_BODY_2(__VA_ARGS__) NTH_LOOP_A_2
#define NTH_LOOP_A_2(...)   , NTH_LOOP_BODY_2(__VA_ARGS__) NTH_LOOP_B_2
#define NTH_LOOP_B_2(...)   , NTH_LOOP_BODY_2(__VA_ARGS__) NTH_LOOP_A_2
#define NTH_LOOP_0_2_END
#define NTH_LOOP_0_2_END_C
#define NTH_LOOP_0_2_C_END
#define NTH_LOOP_0_2_C_END_C ,
#define NTH_LOOP_A_2_END
#define NTH_LOOP_A_2_END_C ,
#define NTH_LOOP_B_2_END
#define NTH_LOOP_B_2_END_C ,
#define NTH_LOOP_BODY_2(arg0_, arg1_, arg2_, ...) DEPAREN(arg2_)

#define NTH_LOOP_0_3(...)     NTH_LOOP_BODY_3(__VA_ARGS__) NTH_LOOP_A_3
#define NTH_LOOP_0_3_C(...) , NTH_LOOP_BODY_3(__VA_ARGS__) NTH_LOOP_A_3
#define NTH_LOOP_A_3(...)   , NTH_LOOP_BODY_3(__VA_ARGS__) NTH_LOOP_B_3
#define NTH_LOOP_B_3(...)   , NTH_LOOP_BODY_3(__VA_ARGS__) NTH_LOOP_A_3
#define NTH_LOOP_0_3_END
#define NTH_LOOP_0_3_END_C
#define NTH_LOOP_0_3_C_END
#define NTH_LOOP_0_3_C_END_C ,
#define NTH_LOOP_A_3_END
#define NTH_LOOP_A_3_END_C ,
#define NTH_LOOP_B_3_END
#define NTH_LOOP_B_3_END_C ,
#define NTH_LOOP_BODY_3(arg0_, arg1_, arg2_, arg3_, ...) DEPAREN(arg3_)

#define DECLARE_METHOD(ret_, func_, ...) \
    template <typename ... ARGS>\
    inline ret_ func_(ARGS&&... args) {\
        return execute_later(this, &func_, std::forward<ARGS>(args)...);\
    }\
    ret_ func_##_WRAPPER(PARAMS_LOOP(__VA_ARGS__))

#define DEFINE_METHOD(ret_, func_, ...) \
    ret_ func_##_WRAPPER(PARAMS_LOOP(__VA_ARGS__))

CodePudding user response:

You don't need the exact name of the class, any thing that resolves to the class's type would serve in the pointer to member syntax. Since you you are inside a non-static member function, simply make the macro expand to

template <typename ... ARGS>
int methodName(ARGS&&... args) {
    return execute_later(this, &std::remove_cv_t<std::remove_reference_t<decltype(*this)>>::methodName_WRAPPER, std::forward<ARGS>(args)...);
}

That's a mouthful, but seeing as its behind a macro it won't stick out too much.

As far the making all the _WRAPPER methods private, that's slightly more tricky. You could of course go full on Java and generate

public:  int foo(...) { ... }
private: int foo_WRAPPER(...) { ... }

But it will affect any declaration after the macro, regardless of how you order the two declarations within. That's less than ideal.

Another solution is to do something with the passkey idiom. Add a tag type into the class somehow. CRTP is one way:

template<class D>
class MethodDeclarer {
    struct tag_t{ explicit tag_t() = default; };
    friend D;
};

Then you use it in conjunction with the macros

class Test : MethodDeclarer<Test> {
};

What does it give us? A private type unique to Test that only it can access. You can then have the macros expand to

template <typename ... ARGS>
int methodName(ARGS&&... args) {
    return execute_later(this, &std::remove_cv_t<std::remove_reference_t<decltype(*this)>>::methodName_WRAPPER, tag_t{}, std::forward<ARGS>(args)...);
}
int methodName_WRAPPER(tag_t, int a, AMoveOnlyType b);

Sure, the _WRAPPER is public, but it needs a tag_t to be called, and only Test can create those. This technique essentially turns access specifiers into values to be passed around.

  • Related