白痴到纯粹的转发

Idiom to pure forwarding

本文关键字:转发 纯粹 白痴      更新时间:2023-10-16

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

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;
}

正如@ipc在我上面提出的第二个问题中所示,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::stringstd::string&std::string&&或它们的常量版本,则会选择其他构造函数,如果没有其他构造函数,则会抛出编译器错误,表示std::enable_if<false, void>::type不存在。

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

在这种情况下使用宏是否危险?总的来说,这是一个好的或有用的idiom还是什么都不是?

我不会使用宏。

在某些情况下,宏是唯一的可能性,但我敢说事实并非如此。宏执行纯文本处理;他们处理的不是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;
};