Reputation: 12259
Mixing this question and this other question, I have arrived to the next (pretty simple indeed) solution: the idea is make available type alias only in the scope of the actual function and check template conditions in a appropiate point:
template<typename... garbage>
struct firewall
{
typedef typename std::enable_if<sizeof...(garbage) == 0>::type type;
};
#define FIREWALL_CALL typename = typename firewall<garbage...>::type
#define TMPL_FIREWALL typename... garbage, FIREWALL_CALL
#define TMPL_ALIAS typename
#define TMPL_CONDITION(...) typename = \
typename std::enable_if<__VA_ARGS__::value>::type
With this code, we can to add some alias or conditions that we want in a comfortable way. This code:
// Erase only qualifiers and references.
template<typename target>
struct pluck
{
typedef typename std::remove_cv<
typename std::remove_reference<target>::type>::type type;
}
// `some_fun` wants ensure its arguments are passed by non-constant values.
template<typename T>
typename pluck<T>::type
some_fun(typename pluck<T>::type a, typename pluck<T>::type b)
{
typename pluck<T>::type c;
// something
return c;
}
becomes (only some_fun
):
template<typename T, TMPL_FIREWALL,
TMPL_ALIAS friendly = typename pluck<T>::type
TMPL_CONDITION(std::is_copy_constructible<friendly>)>
friendly some_fun(friendly a, friendly b)
{
friendly c;
// something
return c;
}
The purpose of the firewall
, as @ipc shows in the second question that I put above, is to absorb any argument that could reemplace a local type alias defined as default template argument.
And also this make available an useful way to avoid other deeper problem: when you want to make a function parametric only for doing a perfect forwarding of an in advance known type; for example, in the next situation:
struct A
{
template<typename... Args>
A(Args&&... args) : _b(std::forward<Args>(args)...)
{}
template<typename Str>
A(Str&& str) : _str(std::forward<Str>(str))
{}
B _b;
std::string _str;
};
If you want to initialize _str
using the perfect forwarding mechanism, unavoidably it becomes an ambiguity with any other template argument. This could be avoid easily with the following additional macro:
#define TMPL_PURE_FORWARDING(a, b) TMPL_FIREWALL, \
TMPL_CONDITION(std::is_same<typename _f_pluck<a>::type, b>)
struct A
{
template<typename... Args>
A(Args&&... args) : _b(std::forward<Args>(args)...)
{}
template<typename fwStr, TMPL_PURE_FORWARDING(fwStr, std::string)>
A(fwStr&& str) : _str(std::forward<fwStr>(str))
{}
B _b;
std::string _str;
};
If fwStr
isn't of type std::string
, std::string&
, std::string&&
or their constant versions, other constructor will be choosen, and if there isn't other constructor, a compiler error will be throw, saying that std::enable_if<false, void>::type
doesn't exists.
Question: in C++ always is preferable to avoid the use of macros, but, it is well-known templates are verbose, and these situations (specially the second one) are very common, or at least in my experience. Then, these macros are very useful.
Are dangerous the use of macros in this situation? Is this in general a good or useful idiom
or nothing is as it seems?
Upvotes: 0
Views: 235
Reputation: 126432
I would not use macros.
There are situations where macros are the only possibility, but I dare to say this is not the case. Macros perform bare text-processing; what they process is not part of the C++ meta-model, but meaningless pieces of text. They are unsafe, hard to understand, and hard to maintain. So unless there is really no other way of doing something, rather avoid macros.
Moreover, your pluck<>
trait basically does what std::decay<>
does. This means that with a simple template alias, you can rewrite your some_fun
function in a way that makes it easy to read and to parse (I got lost trying to put together all the pieces of those macros).
#include <type_traits>
template<typename T>
using Decay = typename std::decay<T>::type;
template<typename T>
Decay<T> some_fun(Decay<T> a, Decay<T> b)
{
Decay<T> c;
// something
return c;
}
Similarly, for the second use case you can write something like:
template<typename T, typename U>
using CheckType = typename std::enable_if<
std::is_same<typename std::decay<T>::type, U>::value
>::type;
struct A
{
template<typename... Args>
A(Args&&... args) : _b(std::forward<Args>(args)...)
{}
template<typename T, CheckType<T, std::string>* = nullptr>
A(T&& str) : _str(std::forward<T>(str))
{}
B _b;
std::string _str;
};
Upvotes: 1