Where and when should we use compile-time macro concatenation?

I saw this piece of code which I thought was great because it saved me from rewriting the getter member functions.

#define GET(n_,N_)                      \
        template<typename T>            \
        const T &get##N_() const        \
        {                               \
            return n_;                  \
        }                               \

      

From what I know, macros "write" code at compile time, so they introduce a function template inside each class, and since it is a template, it can compensate for any type. So I ended up with this:

class Foo
{
   int m_a;
   GET(m_a,A)
};

      

then I used like this:

std::cout<< foo->getA<int>() <<std::endl;

      

My question is WHEN and WHERE should I use macro templates? And is there a way to NOT specify the type when you call the getA member function? Is it because it's in a different namespace?

+3


source to share


2 answers


I'm going to assume that mixing macros and templates will result in you picking up the weaknesses of both, as you may have noticed. The return type of a function template can never be inferred, so you will always need to specify it. BUT luckily there is a solution, and it involves writing a type in your macro:

#define GETTABLE_ATTR(type, name) \
private:
    type name_; \
public:
    type get_##name() const \
    { \
        return name_; \
    }

      



If you use this macro, the good idea is still subjective - remember that you only write the code once, and your best bet is to write it in a way that will prevent mistakes while writing them. And will keep the code simple.

+1


source


As with c++14

, there is still a need to use c macros.

I have two very commonly used macros combined with MTP to get a compile time constant that tells me if a method or attribute exists in the class. It just needs the name of the function, which cannot be given as a parameter to the template. So I prepared this inside a c macro that "writes" my template, which can then be used inside the enable_if clause.

I personally don't like your idea of ​​"automatic getters", but that's just a matter of taste.

As always in programming: if it helps, it is not "undefined behavior", is well documented in the code and cannot be done with a simpler movement since the use of c macros is allowed. For me, macros are a kind of "self-defense" for non-integrated language functions.

Another popular example is an enumeration with associated text for some kind of reflection or for serialization.



An example to catch the existence of a method:

#define DECLARE_HAS_METHOD_FULL_SIGNATURE(NAME) \
template<typename, typename T>\
struct has_method_full_signature_ ## NAME {\
static_assert(\
        std::integral_constant<T, false>::value,\
        "Second template parameter needs to be of function type.");\
};\
\
\
template<typename C, typename Ret, typename... Args>\
struct has_method_full_signature_ ## NAME <C, Ret(Args...)> {\
        template<typename T>\
        static constexpr auto check(T*)\
        -> typename\
        std::is_same<\
        decltype( std::declval<T>(). NAME ( std::declval<Args>()... ) ),\
        Ret   \
        >::type;  \
        \
        template<typename>\
        static constexpr std::false_type check(...);\
        \
        using type = decltype(check<C>(0));  \
        static constexpr bool value = type::value;\ 
}

      

EDIT: Add some example code on how to use this c-macro stuff here.

 #include <utility>
#include <iostream>
#include "component/mytypes_traits.h"

DECLARE_HAS_METHOD_PARMS_ONLY(funny);

DECLARE_HAS_METHOD_FULL_SIGNATURE(f1);
DECLARE_HAS_METHOD_FULL_SIGNATURE(f2);
DECLARE_HAS_METHOD_FULL_SIGNATURE(f3);


class A { public: void funny() {} };

class B { public: void dummy() {} };

class C
{   
    public:
    int f1(int) { return 1;} 
    float f2(int,int) {return 2.0;}
    int f3() { return 1;} 
};  

int main()
{   
    std::cout << has_method_parms_only_funny<A>::value << std::endl;
    std::cout << has_method_parms_only_funny<B>::value << std::endl;
    std::cout << "--" << std::endl;

    std::cout << has_method_full_signature_f1< C, int()>::value << std::endl;
    std::cout << has_method_full_signature_f1< C, int(int)>::value << std::endl;
    std::cout << has_method_full_signature_f1< C, int(int,int)>::value << std::endl;
    std::cout << "--" << std::endl;

    std::cout << has_method_full_signature_f2< C, float()>::value << std::endl;
    std::cout << has_method_full_signature_f2< C, float(int)>::value << std::endl;
    std::cout << has_method_full_signature_f2< C, float(int,int)>::value << std::endl;
    std::cout << "--" << std::endl;

    std::cout << has_method_full_signature_f3< C, int()>::value << std::endl;
    std::cout << has_method_full_signature_f3< C, int(int)>::value << std::endl;
    std::cout << has_method_full_signature_f3< C, int(int,int)>::value << std::endl;
    std::cout << "--" << std::endl;
}   

      

+1


source







All Articles