【问题标题】:Template specialization and perfect forwarding模板专业化和完美转发
【发布时间】:2017-12-29 01:55:58
【问题描述】:

所以我正在尝试构建一个“清理器”函数来过滤格式化参数,然后再将它们转发到printf

template<typename A>
_CR_INLINE decltype(auto) sanitize_forward(A&& arg) {
    return std::forward<A>(arg);
}

template<>
_CR_INLINE decltype(auto) sanitize_forward<std::string>(std::string&& arg) {
    return std::forward<const char*>(arg.c_str());
}

所以每个std::string 都应该“衰减”成const char*,以便正确格式化。

template<typename...Args>
_CR_INLINE void printf_safe(const std::string& format, Args&&...args) {
    std::printf(format.c_str(), sanitize_forward<Args>(args)...);
}

我希望将参数完美地转发printf,这就是我返回std::forward的原因。但我无法真正理解这应该如何实现。

1) decltype(auto) 正确吗?应该保留std::forward返回的r值引用性吧?

2) 我应该如何专门化我的模板:std::string&amp;&amp;std::string

3) 推导出的转发引用应该和实际的类型一样吧?

【问题讨论】:

  • printf 不需要转发。
  • 但是我们假设 printf 是一些内部日志功能,应该如何实现呢?另外为什么不需要转发参数?

标签: c++ c++11


【解决方案1】:

您几乎完全误解了转发引用;这是意料之中的,它们令人困惑。

要了解它们,您必须了解引用折叠规则、std::forward 本身、推导T&amp;&amp; 时的模板参数推导规则,以及它们是如何联系在一起的,至少在某种程度上是这样。

首先,引用折叠。看看这张图表:

If       Then      Then
X is     X& is     X&& is
T        T&        T&&
T&       T&        T&
T const& T const&  T const&
T&&      T&        T&&

如果Xint,那么X&amp;&amp;int&amp;&amp;。如果Xint&amp;,那么X&amp;&amp;int&amp;

再读一遍。再次。再一次。当两者都应用时,&amp; 胜过 &amp;&amp;

接下来,扣除。我会在这里说谎,但他们正在简化谎言。

如果您将Foo&amp; 传递给模板推导出X&amp;&amp;,则XFoo&amp; 并且X&amp;&amp;Foo&amp;

如果您将Foo&amp;&amp; 传递给模板推导出X&amp;&amp;,则XFoo 并且X&amp;&amp;Foo&amp;&amp;

接下来,std::forward 是一个条件移动。对于引用变量X&amp;&amp; xstd::forward&lt;X&gt;(x)decltype(x)(x)——它将x 转换为它被声明为的类型。如果x 是一个右值引用,它会将x 转换为一个右值引用。这是必需的,因为表达式 x 的类型是 X&amp; 而不是 X&amp;&amp;,即使 x 是右值引用。右值引用是右值的引用,但它本身不是右值。

现在修复您的代码。


template<class T>
struct tag_t{constexpr tag_t(){}};
template<class T>
constexpr tag_t<std::decay_t<T>> tag{};

template<class T>
auto sanitizer(tag_t<T>){
  return [](auto&& t)->decltype(auto){
    return decltype(t)(t);
  };
}
template<class A>
decltype(auto) sanitize(A&& arg) {
  return sanitizer(tag<A>)(std::forward<A>(arg));
}

auto sanitizer(tag_t<std::string>) {
  return [](std::string const& s){return s.c_str();};
}

template<class...Ts>
void printf_safe(const std::string& format, Ts&&...ts) {
  std::printf(format.c_str(), sanitize(std::forward<Ts>(ts))...);
}

我遵循 SRP(单一责任原则)——我从 sanitize 中分离出来。

然后我将哪个消毒动作 (sanitizer) 与实际执行 (sanitize) 分开。

这让我写了一次std::string 清理代码,没有“意外”获得完美转发。

顺便说一句,如果您想将数组 args 视为非指针,我将用 remove ref 替换衰减并删除 cv。

您可以通过在 tag_t 的命名空间或我们正在清理的类型的命名空间中编写 sanitizer 重载来扩展 sanitize 功能(在 std 中自然没有)。

【讨论】:

    【解决方案2】:

    所以我正在尝试构建一个“清理器”函数来过滤格式化参数,然后再将它们转发到printf

    你的前提似乎是错误的。 printf 是一个使用 va_arg 的 C 函数 - 它不需要或不需要任何完美的转发。

    http://en.cppreference.com/w/cpp/io/c/fprintf

    它也永远不会“消耗”它的论点,而只是从中读取。区分临时和非临时是没有意义的 - 只需在 printf 包装器中使用 const Args&amp;...


    1) decltype(auto) 正确吗?应该保留std::forward返回的r值引用性吧?

    std::forward 将返回一个左值引用右值引用decltype(auto) 确实会保留这一点。

    您对std::stringsanitize_forward 专业化看起来没有用-std::forward&lt;const char*&gt; 将始终返回const char* &amp;&amp;。我不认为它与:

    template<>
    _CR_INLINE const char* sanitize_forward<std::string>(std::string&& arg) {
        return arg.c_str();
    }
    

    此外,将.c_str()右值引用返回到std::string 听起来非常危险且不正确:您正在获取一个指向即将到期的字符串的内部缓冲区的指针。您可能想在这里使用const std::string&amp;


    2) 我应该如何专门化我的模板:std::string&amp;&amp;std::string

    怎么称呼?您是否明确提供了模板参数?模板参数是否总是非引用,还是既是左值引用又是非引用

    既然你有sanitize_forward&lt;Args&gt;,你可能会尝试同时调用这两个...

    • sanitize_forward&lt;std::string&gt;

    • sanitize_forward&lt;std::string&amp;&gt;

    ...也许有 cv-qualifiers。您可能需要提供一个额外的显式 std::decay_t&lt;Args&gt; 参数来处理“专业化”业务。


    3) 推导出的转发引用应该和实际的类型一样吧?

    不确定你的意思。能详细点吗?

    【讨论】:

      猜你喜欢
      • 2023-03-28
      • 1970-01-01
      • 2011-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-18
      相关资源
      最近更新 更多