Template specialization for rvalues (模板特化用于右值)

huangapple go评论75阅读模式
英文:

Template specialization for rvalues

问题

Here's the translated code:

我有一组紧凑的函数,用于将任意数据写入逗号分隔值文件。它看起来像这样:

```cpp
template<typename T>
void log(std::ostream& out, T a) {
    out << a << std::endl;
}

template<typename T, typename... Args>
void log(std::ostream& out, T a, Args... args) {
    out << a << ",";
    log(out, args...);
}

int main()
{
	log(std::cout, 1, "a");
}

这将输出我期望的内容:

1,a

然而,当写入具有嵌入逗号的字符串时,值应该用引号括起来。所以我想要:

log(1, "a, b");

输出:

1,"a, b"

而不是:

1,a, b

我尝试通过添加类似于这样的引用函数来解决这个问题:

/* 对于任何旧的类型,只需将其写出即可。 */
template<typename T>
void quote(std::ostream& out, T a) {
    out << a;
}

/* 为std::string进行特殊化。 */
template<>
void quote<std::string>(std::ostream& out, std::string a) {
    if (a.find(',') == std::string::npos) {
        out << a;
    }
    else {
        out << '"' << a << '"';
    }
}

/* 记录单个值。 */
template<typename T>
void log(std::ostream& out, T a) {
   quote(out, a);
   out << std::endl;
}

/** 记录多个值。 */
template<typename T, typename... Args>
void log(std::ostream& out, T a, Args... args) {
    quote(out, a);
    out << ",";
    log(out, args...);
}

如果我记录数据作为左值,这将起作用。

std::string text {"a, b"};
log(std::cout, 1, text);

会产生预期的输出:

1,"a, b"

但是传递右值似乎不起作用。

我尝试为std::string& std::string&& std::string const&和std::string const&&进行特殊化,但似乎没有什么作用。

以下是我尝试的所有内容:

(以下为你提供的一系列尝试代码,已翻译)


这就是翻译后的代码。如果有其他需要,请随时提出。

<details>
<summary>英文:</summary>

I have a compact set of functions that I use to write arbitrary data to a comma separated values file. It looks something like this:

```cpp
template&lt;typename T&gt;
void log(std::ostream&amp; out, T a) {
    out &lt;&lt; a &lt;&lt; std::endl;
}

template&lt;typename T, typename... Args&gt;
void log(std::ostream&amp; out, T a, Args... args) {
    out &lt;&lt; a &lt;&lt; &quot;,&quot;;
    log(out, args...);
}

int main()
{
	log(std::cout, 1, &quot;a&quot;);
}

Which outputs what I would expect:

1,a

However, when writing strings that have embedded commas the values should be wrapped in quotes. So I want:

log(1, &quot;a, b&quot;);

To output:

1,&quot;a, b&quot;

Not:

1,a, b

I tried to address this by adding quoting functions that look like this:

/* For any old type, just write it out. */
template&lt;typename T&gt;
void quote(std::ostream&amp; out, T a) {
    out &lt;&lt; a;
}

/* Specialize for std::string. */
template&lt;&gt;
void quote&lt;std::string&gt;(std::ostream&amp; out, std::string a) {
    if (a.find(&#39;,&#39;) == std::string::npos) {
        out &lt;&lt; a;
    }
    else {
        out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
    }
}

/* Log a single value. */
template&lt;typename T&gt;
void log(std::ostream&amp; out, T a) {
   quote(out, a);
   out &lt;&lt; std::endl;
}

/** Log multiple values. */
template&lt;typename T, typename... Args&gt;
void log(std::ostream&amp; out, T a, Args... args) {
    quote(out, a);
    out &lt;&lt; &quot;,&quot;;
    Log(out, args...);
}

If I log the data as an lvalue this works.

std::string text {&quot;a, b&quot;};
log(std::cout, 1, text);

Produces the expected output:

1,&quot;a, b&quot;

But passing rvalues does not seem to work.

I tried specializing the quote method for std::string& std::string&& std::string const&, and std::string const&&, but nothing seems to work.

Below is the full set of things I tried:

// Example program
#include &lt;iostream&gt;
#include &lt;string&gt;

/* For any old type, just write it out. */
template&lt;typename T&gt;
void quote(std::ostream&amp; out, T a) {
    out &lt;&lt; a;
}

/* Output a string wrapped in &quot;&#39;s if it contains a comma. */
template&lt;&gt;
void quote&lt;std::string&gt;(std::ostream&amp; out, std::string a) {
    if (a.find(&#39;,&#39;) == std::string::npos) {
        out &lt;&lt; a;
    }
    else {
        out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
    }
}

/* Output a string wrapped in &quot;&#39;s if it contains a comma. */
template&lt;&gt;
void quote&lt;std::string const&gt;(std::ostream&amp; out, std::string const a) {
    if (a.find(&#39;,&#39;) == std::string::npos) {
        out &lt;&lt; a;
    }
    else {
        out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
    }
}

/* Output a string wrapped in &quot;&#39;s if it contains a comma. */
template&lt;&gt;
void quote&lt;std::string const&amp;&gt;(std::ostream&amp; out, std::string const&amp; a) {
    if (a.find(&#39;,&#39;) == std::string::npos) {
        out &lt;&lt; a;
    }
    else {
        out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
    }
}

/* Output a string wrapped in &quot;&#39;s if it contains a comma. */
template&lt;&gt;
void quote&lt;std::string const&amp;&amp;&gt;(std::ostream&amp; out, std::string const&amp;&amp; a) {
    if (a.find(&#39;,&#39;) == std::string::npos) {
        out &lt;&lt; a;
    }
    else {
        out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
    }
}

/* log a single value. */
template&lt;typename T&gt;
void log(std::ostream&amp; out, T a) {
   quote(out, a);
   out &lt;&lt; std::endl;
}

/** log multiple values. */
template&lt;typename T, typename... Args&gt;
void log(std::ostream&amp; out, T a, Args... args) {
    quote(out, a);
    out &lt;&lt; &quot;,&quot;;
    log(out, args...);
}


int main()
{
    std::string b { &quot;a, b&quot; };
   
    log(std::cout, 1, b);
    log(std::cout, 1, &quot;c, d&quot;);
}

Which outputs:

1,&quot;a, b&quot;
1,c, d

Note: For the project I'm working on we're limited to C++11 and boost is not available.

答案1

得分: 3

以下是您要翻译的内容:

原因是,对于像"c, d"这样的值,你的特化不起作用,因为它的类型是char const*,而不是std::string。除非参数类型恰好匹配其中一种特化,否则将默认调用主quote模板。你的例子中,std::string b之所以工作是因为有std::string特化(而不是std::string const&特化)。

你可以添加更多的特化,例如这样:

template<>
void quote<char const*>(std::ostream& out, char const* a) {
    if (std::string(a).find(',') == std::string::npos) {
        out << a;
    }
    else {
        out << '"' << a << '"';
    }
}

然而,我认为这并不能解决你的问题。你实际上想要处理任何可以转换为std::string的情况。

此外,你可能想以常量引用的方式接受参数。

你可以在一个函数中编写整个内容,前提是你使用的是C++17(对于if constexpr):

template<typename T, typename... Args>
void log(std::ostream& out, T const& a, Args const&... args) {
    if constexpr (std::is_convertible_v<T, std::string>) {
        if (static_cast<std::string const&>(a).find(',') != std::string::npos) {
            out << '"' << a << '"';
        } else {
            out << a;
        }
    } else {
        out << a;
    }
    
    if constexpr (sizeof...(args) > 0) {
        out << ",";
        log(out, args...);
    } else {
        out << std::endl;
    }
}

这是一个稍微复杂一点的版本,使用了折叠表达式而不是递归:

template<typename T, typename... Args>
void log(std::ostream& out, T const& a, Args const&... args) {
    auto quote = [&out](auto const& a) {
        if constexpr (std::is_convertible_v<decltype(a), std::string>) {
            if (static_cast<std::string const&>(a).find(',') != std::string::npos) {
                out << '"' << a << '"';
            } else {
                out << a;
            }
        } else {
            out << a;
        }
    };

    quote(a);
    ((out << ',', quote(args)), ...);
    out << std::endl;
}

在C++17之前,可以使用标签分派来实现相同的功能:

template<typename T>
void quote(std::ostream& out, T const& a, std::true_type) {
    if (static_cast<std::string const&>(a).find(',') == std::string::npos) {
        out << a;
    } else {
        out << '"' << a << '"';
    }
}

template<typename T>
void quote(std::ostream& out, T const& a, std::false_type) {
    out << a;
}

template<typename T>
void log(std::ostream& out, T const& a) {
   quote(out, a, std::is_convertible<T, std::string>{});
   out << std::endl;
}

template<typename T, typename... Args>
void log(std::ostream& out, T const& a, Args&&... args) {
    quote(out, a, std::is_convertible<T, std::string>{});
    out << ",";
    log(out, std::forward<Args>(args)...);
}

希望这些翻译对您有所帮助。

英文:

The reason your specializations don't work for a value such as &quot;c, d&quot; is because its type is char const*, not std::string. The main quote template will be called by default unless the argument type exactly matches one of the specializations. Your example with std::string b works because of the std::string specialization (and not for example because of the std::string const&amp; one).

You could add more specializations, like this for example:

template&lt;&gt;
void quote&lt;char const*&gt;(std::ostream&amp; out, char const* a) {
if (std::string(a).find(&#39;,&#39;) == std::string::npos) {
out &lt;&lt; a;
}
else {
out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
}
}

However, I don't think this solves your problem. What you actually want is to handle any case where the argument is convertible to a std::string.

Also, you probably want to take the arguments by const reference.

You can write the whole thing in one function, provided you are using C++17 (for if constexpr):

template&lt;typename T, typename... Args&gt;
void log(std::ostream&amp; out, T const&amp; a, Args const&amp;... args) {
if constexpr (std::is_convertible_v&lt;T, std::string&gt;) {
if (static_cast&lt;std::string const&amp;&gt;(a).find(&#39;,&#39;) != std::string::npos) {
out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
} else {
out &lt;&lt; a;
}
} else {
out &lt;&lt; a;
}
if constexpr (sizeof...(args) &gt; 0) {
out &lt;&lt; &quot;,&quot;;
log(out, args...);
} else {
out &lt;&lt; std::endl;
}
}

Demo

Here is a slightly more complex version using a fold expression instead of recursion:

template&lt;typename T, typename... Args&gt;
void log(std::ostream&amp; out, T const&amp; a, Args const&amp;... args) {
auto quote = [&amp;out](auto const&amp; a) {
if constexpr (std::is_convertible_v&lt;decltype(a), std::string&gt;) {
if (static_cast&lt;std::string const&amp;&gt;(a).find(&#39;,&#39;) != std::string::npos) {
out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
} else {
out &lt;&lt; a;
}
} else {
out &lt;&lt; a;
}
};
quote(a);
((out &lt;&lt; &#39;,&#39;, quote(args)), ...);
out &lt;&lt; std::endl;
}

Demo

Prior to C++17, the same thing can be achieved with tag dispatching:

template&lt;typename T&gt;
void quote(std::ostream&amp; out, T const&amp; a, std::true_type) {
if (static_cast&lt;std::string const&amp;&gt;(a).find(&#39;,&#39;) == std::string::npos) {
out &lt;&lt; a;
} else {
out &lt;&lt; &#39;&quot;&#39; &lt;&lt; a &lt;&lt; &#39;&quot;&#39;;
}
}
template&lt;typename T&gt;
void quote(std::ostream&amp; out, T const&amp; a, std::false_type) {
out &lt;&lt; a;
}
template&lt;typename T&gt;
void log(std::ostream&amp; out, T const&amp; a) {
quote(out, a, std::is_convertible&lt;T, std::string&gt;{});
out &lt;&lt; std::endl;
}
template&lt;typename T, typename... Args&gt;
void log(std::ostream&amp; out, T const&amp; a, Args&amp;&amp;... args) {
quote(out, a, std::is_convertible&lt;T, std::string&gt;{});
out &lt;&lt; &quot;,&quot;;
log(out, std::forward&lt;Args&gt;(args)...);
}

Demo

huangapple
  • 本文由 发表于 2023年5月25日 00:02:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76325449.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定