混合this question和这个other question,我已经到了下一个(非常简单的确实)解决方案:想法是仅在实际函数的范围内提供类型别名并检查模板条件合适的一点:
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
使用此代码,我们可以以舒适的方式添加一些我们想要的别名或条件。这段代码:
// 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;
}
变为(仅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;
}
我在上面提到的第二个问题中显示的firewall
的目的是吸收任何可以重新设置定义为默认模板参数的本地类型别名的参数。
并且这也提供了一种有用的方法来避免其他更深层次的问题:当你想要一个函数参数化时,只是为了完美地转发一个预先知道的类型;例如,在下一种情况下:
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;
};
如果您想使用完美的转发机制初始化_str
,则不可避免地会出现与任何其他模板参数的歧义。使用以下附加宏可以轻松避免这种情况:
#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;
};
如果fwStr
不是std::string
,std::string&
,std::string&&
或其常量版本,则会选择其他构造函数,如果没有其他构造函数,抛出编译器错误,说std::enable_if<false, void>::type
不存在。
问题:在C ++中总是最好避免使用宏,但是,众所周知的模板很冗长,而且这些情况(特别是第二种情况)很常见,或者至少在我的经验中。然后,这些宏非常有用。
在这种情况下使用宏是危险的吗?这通常是好的还是有用的idiom
还是看起来没什么?
答案 0 :(得分:1)
我不会使用宏。
有些情况下,宏是唯一的可能性,但我敢说这不是这种情况。宏执行裸文本处理;他们处理的不是C ++元模型的一部分,而是无意义的文本。它们不安全,难以理解,难以维护。所以,除非没有其他方法可以做某事,而是避免使用宏。
此外,您的pluck<>
特质基本上与std::decay<>
的作用相同。这意味着使用简单的模板别名,您可以以一种易于阅读和来解析的方式重写some_fun
函数(我迷失了尝试将所有部分组合在一起)那些宏)。
#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;
}
同样,对于第二个用例,您可以编写如下内容:
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;
};