template/macro for reducing redundant functions?
-
This is a simple example, but it basically is a bunch of redundant class methods:
class JMsg { ... JMsg& arg(bool arg){ m_args.append(QJsonValue(arg)); return *this; } JMsg& arg(int arg){ m_args.append(QJsonValue(arg)); return *this; } JMsg& arg(double arg){ m_args.append(QJsonValue(arg)); return *this; } JMsg& arg(const QString arg){ m_args.append(QJsonValue(arg)); return *this; } JMsg& arg(const char* arg){ m_args.append(QJsonValue(arg)); return *this; } ... };
It feels like a candidate for templating, but all I could think of was this:
template<class T> JMsg& arg(T arg){ m_args.append(QJsonValue(arg)); return *this; }
But the code to use it is ugly:
JMsg().arg<int>().arg<float>()
Is there a way to make the template private and declare a member function?
class JMsg { private: template<class T> JMsg& arg(T arg){ m_args.append(QJsonValue(arg)); return *this; } public: arg<int>; // I know this won't work, but it would nicer if it could create members this way. };
My other thought was using a macro, but that gets ugly too, but at least it can create class members.
#define JMSG_ARG(atype) \ JMsg& arg(atype arg){ \ m_args.append(QJsonValue(arg)); \ return *this; \ } \ ... JMSG_ARG(float)
Like I said, macros are ugly but effective. I hope I am just completely misunderstanding how templates can be used.
-
Why do I always find the answer after I post...
class JMsg{ ... public: template<class T> JMsg& arg(T arg){ m_args.append(QJsonValue(arg)); return *this; } ... }; template JMsg& JMsg::arg<bool>(bool arg); template JMsg& JMsg::arg<int>(int arg);
What I don't like is that the template function is public. I don't want another programmer to define arbitrary types to use with the class. The ones selected are the ones I want to support. I cannot define the template with a private version. I tried making it a friend template, but then it cannot access members like it did before. I am not processing the object passed to the function, so I guess that makes sense. If there were a member function version of friend classes it might work.
-
@fcarney said in template/macro for reducing redundant functions?:
What I don't like is that the template function is public. I don't want another programmer to define arbitrary types to use with the class. The ones selected are the ones I want to support. I cannot define the template with a private version. I tried making it a friend template, but then it cannot access members like it did before. I am not processing the object passed to the function, so I guess that makes sense. If there were a member function version of friend classes it might work.
Then don't define the template function. Hide it's implementation in the source. That is to say - use templates as they were originally meant - as a sophisticated copy paste machinery.
header
class JMsg { public: template <typename T> JMsg & arg(T x); // ... more }; extern template JMsg & JMsg::arg<float>(float arg); extern template JMsg & JMsg::arg<bool>(bool arg);
source
template <typename T> JMsg & JMsg::arg(T arg) { m_args.append(QJsonValue(arg)); return *this; } template JMsg & JMsg::arg<bool>(bool arg); template JMsg & JMsg::arg<int>(int arg);
-
@kshegunov said in template/macro for reducing redundant functions?:
Hide it's implementation in the source.
No, I mean as in can use arbitrary types. Not hiding code implementation. If the template could be made "private" as in not accessible outside the scope of the object. If the template was private and I could define a member function based upon the template then I could lock down the types with less duplication of code. However, they just don't seem to work that way.
What I am reading is that C++ doesn't have a whole lot of ways to limit type use in templates.
I also found I don't need this at all:
template JMsg& JMsg::arg<bool>(bool arg); template JMsg& JMsg::arg<int>(int arg);
For my current task I am just going to accept that I either can define the functions per type or use a template to reduce the code duplication. I can live with the too many types issue as I believe at some point the QJsonValue object will complain during compile.
-
Okay, lets say I have this:
class JMsg{ ... public: template<class T> JMsg& arg(T arg){ m_args.append(QJsonValue(arg)); return *this; } ... };
Without anything else this allows me to do this:
JMsg().arg(1); // an int JMsg().arg("hello"); // a const char* etc
But if I don't want this:
JMsg().arg(QJsonArray()); // which QJsonValue will accept
My down stream code will probably do strange things with this. So I want the limited typing a explicitly defined member function gives, but with the copy pasta that templates provide. These are 2 opposing incompatible goals. However, if there was a way to use the template as a private to the class scope, and then define the member function using the template per type specified then it would prevent the use of problematic types. I guess if I really wanted to I could have it call a private template function, but I am wondering if it is really worth all this trouble. I guess what I really want is the functionality of a dumb macro, but with nicer syntax.
-
@fcarney said in template/macro for reducing redundant functions?:
However, if there was a way to use the template as a private to the class scope, and then define the member function using the template per type specified then it would prevent the use of problematic types.
Again, this is exactly what my code does. I did not define the template in the header exactly so you can't call one of its instantiations if it was not instantiated explicitly. Look again at my snippet - you have header and source and what goes where is important.
-
@kshegunov
Okay, I think I get it. The template is partial if it is only provided in the header file. It needs the complete template that only exists in the cpp file. So it doesn't have the information it needs to complete the member function. This is really really sneaky. I found it will actually compile and run without these as well:extern template JMsg& JMsg::arg<bool>(bool arg); extern template JMsg& JMsg::arg<int>(int arg); extern template JMsg& JMsg::arg<double>(double arg); extern template JMsg& JMsg::arg<const QString>(const QString arg); extern template JMsg& JMsg::arg<const char*>(const char* arg);
It does throw up some semantics issues though.
So it pushes the problem to the linker, as in if someone does this:
JMsg().arg(QJsonArray());
It should throw a linker error.
Thank you for taking the time to explain this to me.
-
@fcarney said in template/macro for reducing redundant functions?:
Okay, I think I get it.
Partly. What a template is from the point of view of the binary is nothing - it does not produce anything. A template is way to tell your compiler to substitute template arguments at some point and produce a real living class (or function) which it then can compile to assembly. Instantiation happens on first use with a given set of template parameters ordinarily. In this case I "abuse" that peculiarity by saying: okay, there is a template function (the declaration) but when you need to substitute the types (i.e. instantiate the template on use) you can't, because you don't know the function body.
The
extern template
at the end is to make all that at all useful - fine there's no body, so the template can't be instantiated in the user code, but I can give you some instantiations I made explicitly somewhere else (in this case in the source). So it works like this:- The compiler sees there's a declaration. It also sees there are 2, 3, 4 instantiations that are done somewhere else (i.e. the declarations at the bottom). In the source it has the template body, and has been told to instantiate the template - that's fine, it can as it knows how.
- When you use it somewhere it matches the stuff that's declared (i.e. the template declaration and the instantiations) against what you request. It puts a reference for the linker to resolve.
- If there's an instantiation provided for the given set of template parameters, everything is fine - the linker's going to clean up the mess and link the function you have with the usage. If not, i.e. you used the template with a parameter that hand't been explicitly instantiated, then the linker complains - there's no such thing.