0

混合这个问题另一个问题,我已经到了下一个(确实非常简单)的解决方案:想法是只在实际函数的范围内提供类型别名并在适当的点检查模板条件:

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正如@ipc在我上面提出的第二个问题中所示,的目的是吸收任何可以重新替换定义为默认模板参数的本地类型别名的参数。

这也为避免其他更深层次的问题提供了一种有用的方法:当您只想使函数参数化以对预先已知的类型进行完美转发时;例如,在下一种情况下:

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不是 type std::string,或者它们的常量版本std::string&std::string&&则会选择其他构造函数,如果没有其他构造函数,则会抛出编译器错误,std::enable_if<false, void>::type说不存在。

问题:在 C++ 中总是最好避免使用宏,但是众所周知的模板是冗长的,这些情况(特别是第二种情况)很常见,或者至少在我的经验中是这样。然后,这些宏非常有用。

在这种情况下使用宏是否危险?这通常是好还是有用的idiom,或者看起来什么都没有?

4

1 回答 1

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;
};
于 2013-02-05T00:03:33.587 回答