英文:
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<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");
}
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, "a, b");
To output:
1,"a, b"
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<typename T>
void quote(std::ostream& out, T a) {
out << a;
}
/* Specialize for std::string. */
template<>
void quote<std::string>(std::ostream& out, std::string a) {
if (a.find(',') == std::string::npos) {
out << a;
}
else {
out << '"' << a << '"';
}
}
/* Log a single value. */
template<typename T>
void log(std::ostream& out, T a) {
quote(out, a);
out << std::endl;
}
/** Log multiple values. */
template<typename T, typename... Args>
void log(std::ostream& out, T a, Args... args) {
quote(out, a);
out << ",";
Log(out, args...);
}
If I log the data as an lvalue this works.
std::string text {"a, b"};
log(std::cout, 1, text);
Produces the expected output:
1,"a, b"
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 <iostream>
#include <string>
/* For any old type, just write it out. */
template<typename T>
void quote(std::ostream& out, T a) {
out << a;
}
/* Output a string wrapped in "'s if it contains a comma. */
template<>
void quote<std::string>(std::ostream& out, std::string a) {
if (a.find(',') == std::string::npos) {
out << a;
}
else {
out << '"' << a << '"';
}
}
/* Output a string wrapped in "'s if it contains a comma. */
template<>
void quote<std::string const>(std::ostream& out, std::string const a) {
if (a.find(',') == std::string::npos) {
out << a;
}
else {
out << '"' << a << '"';
}
}
/* Output a string wrapped in "'s if it contains a comma. */
template<>
void quote<std::string const&>(std::ostream& out, std::string const& a) {
if (a.find(',') == std::string::npos) {
out << a;
}
else {
out << '"' << a << '"';
}
}
/* Output a string wrapped in "'s if it contains a comma. */
template<>
void quote<std::string const&&>(std::ostream& out, std::string const&& a) {
if (a.find(',') == std::string::npos) {
out << a;
}
else {
out << '"' << a << '"';
}
}
/* log a single value. */
template<typename T>
void log(std::ostream& out, T a) {
quote(out, a);
out << std::endl;
}
/** log multiple values. */
template<typename T, typename... Args>
void log(std::ostream& out, T a, Args... args) {
quote(out, a);
out << ",";
log(out, args...);
}
int main()
{
std::string b { "a, b" };
log(std::cout, 1, b);
log(std::cout, 1, "c, d");
}
Which outputs:
1,"a, b"
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 "c, d"
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&
one).
You could add more specializations, like this for example:
template<>
void quote<char const*>(std::ostream& out, char const* a) {
if (std::string(a).find(',') == std::string::npos) {
out << a;
}
else {
out << '"' << a << '"';
}
}
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<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;
}
}
Here is a slightly more complex version using a fold expression instead of recursion:
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;
}
Prior to C++17, the same thing can be achieved with tag dispatching:
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)...);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论