键入特征以区分常量字符 [N] 和 std::string
Type traits to distinguish between a const char[N] and a std::string?
如何编写一个可变参数模板,该模板将const char[N]s和std::strings作为参数,但根据参数类型执行不同的行为?
到目前为止,我的可变参数模板如下所示:
template<typename T>
void Capitalize_And_Output(T&& str) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper); //<- will not compile with char*s
std::cout << str << std::endl;
return;
}
template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper); //<- will not compile with char*s
std::cout << str << " ";
Capitalize_And_Output(std::forward<Strings>(rest)...);
return;
}
使用"通用"引用,所有内容都被接受到函数中。
但是,像这样调用函数将不起作用:
std::string hello = "hello";
std::string earth = "earth";
//fails because "planet" is a const char[N] and not a std::string
Capitalize_And_Output(hello,"planet","earth"); //outputs: "HELLO PLANET EARTH"
如果我执行以下操作,它确实有效:
Capitalize_And_Output(hello,std::string("planet"),"earth"); //outputs: "HELLO PLANET EARTH"
但我不希望用户负责进行此转换。 如何将该责任传递到模板函数中?
我一直试图使用类型特征做出决定,但没有成功。我试图使用:
std::is_same<First, std::string&>::value
但不知道如何做出分支决定。 我不相信这在 if 语句中有效。
也许我需要以某种方式使用 std::condition?也许我需要通过在模板中创建一个 auto&&& 类型的局部变量来解决它? 到目前为止,我在尝试过的各种事情上没有任何成功。
我看到 Simple 的解决方案有两个问题:
(1) 编译此测试用例失败
std::string hello = "hello";
const std::string earth = "earth";
Capitalize_And_Output(hello, "planet", earth);
因为earth
是一个const std::string
,没有过载可以接听这个电话。(试试吧!
2)它无法编译可转换为std::string
的类型(const char*
和类似类型除外),例如,
struct beautiful {
operator std::string() const {
return "beautiful";
}
};
Capitalize_And_Output(hello, beautiful{}, "planet", earth);
以下实现解决了这些问题:
新解决方案:我的旧解决方案(如下)有效,但对char*
、char[N]
效率不高。此外,它很复杂,并使用一些重载分辨率技巧来避免歧义。这个更简单,更高效。
void Capitalize_And_Output_impl(const char* str) {
while (char c = toupper(*str++))
std::cout << c;
}
void Capitalize_And_Output_impl(std::string& str) {
std::transform(str.begin(), str.end(), str.begin(), toupper);
std::cout << str;
}
void Capitalize_And_Output_impl(const std::string& str) {
Capitalize_And_Output_impl(str.data());
}
template<typename First>
void Capitalize_And_Output(First&& str) {
Capitalize_And_Output_impl(std::forward<First>(str));
std::cout << 'n';
}
template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
Capitalize_And_Output_impl(std::forward<First>(str));
std::cout << ' ';
Capitalize_And_Output(std::forward<Strings>(rest)...);
}
因为我不使用std::transform
(除了第二个重载),所以它不需要提前知道字符串的大小。因此,对于char*
,无需调用std::strlen
(与其他解决方案一样)。
需要注意的一个小细节是,此实现仅在单词之间打印空格。(它不会在最后一个单词之后打印一个。
旧解决方案:
void Capitalize_And_Output_impl(std::string& str, int) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
std::cout << str << ' ';
}
void Capitalize_And_Output_impl(std::string str, long) {
Capitalize_And_Output_impl(str, 0);
}
void Capitalize_And_Output() {
std::cout << 'n';
}
template<typename First, typename ... Strings>
void Capitalize_And_Output(First&& str, Strings&&... rest) {
Capitalize_And_Output_impl(std::forward<First>(str), 0);
Capitalize_And_Output(std::forward<Strings>(rest)...);
}
我想这两个Capitalize_And_Output_impl
超载值得解释。
首先考虑第二个参数(int
/long
)。第一次重载可以采用非const
左值,这些左值在退出时大写(正如 Trevor Hickney 在对 Simple 解决方案的评论中要求的那样)。
第二个 oveload 旨在取其他所有内容,即右值和const
左值。这个想法是将参数复制到一个左值,然后将其传递给第一个重载。这个函数自然可以用这种方式实现(仍然不考虑第二个参数):
void Capitalize_And_Output_impl(const std::string& str) {
std::string tmp(str);
Capitalize_And_Output_impl(tmp);
}
这根据需要工作。然而,Dave Abrahams 的一篇著名文章解释说,当你通过引用 const
获取参数并将其复制到函数中时(如上所述),最好按值获取参数(因为在某些情况下编译器可能会避免复制)。总之,这种实现是可取的:
void Capitalize_And_Output_impl(std::string str) {
Capitalize_And_Output_impl(str);
}
遗憾的是,对于第一次重载,对左值Capitalize_And_Output_impl
的调用也可以定向到此重载。这会产生编译器抱怨的歧义。这就是为什么我们需要第二个论点。
第一次重载需要int
,第二次需要long
。因此,传递文字0
,这是一个int
,使得第一个重载比第二个重载更可取,但只有当出现歧义时。在其他情况下,即,当第一个参数是右值或const
左值时,第一个重载不能使用,而第二个可以在文字0
提升为 long
之后使用。
最后两点意见。(1)如果你想避免Capitalize_And_Output
中的递归调用(我想这只是一个品味问题),那么你可以使用与Simple的解决方案相同的技巧(通过unpack
)和(2)我认为没有必要像Simple的解决方案那样传递lambda包装::toupper
。
您不需要为此提供类型特征:
char safer_toupper(unsigned char const c)
{
return static_cast<char>(std::toupper(c));
}
void Capitalize_And_Output_Impl(std::string& str)
{
auto const first = str.begin();
std::transform(first, str.end(), first, safer_toupper);
std::cout << str;
}
void Capitalize_And_Output_Impl(std::string const& str)
{
std::transform(str.begin(), str.end(),
std::ostreambuf_iterator<char>(std::cout),
safer_toupper);
}
void Capitalize_And_Output_Impl(char const* const str)
{
std::transform(str, str + std::strlen(str),
std::ostreambuf_iterator<char>(std::cout),
safer_toupper);
}
template<typename... Strings>
void Capitalize_And_Output(Strings&&... rest)
{
int const unpack[]{0, (Capitalize_And_Output_Impl(rest),
std::cout << ' ', 0)...};
static_cast<void>(unpack);
std::cout << std::endl;
}
此版本不做不必要的参数复制,不引入不必要的临时字符串,并避免对编译时已知长度的文本字符串调用strlen()
。
#include <algorithm>
#include <cctype>
#include <cstring>
#include <iostream>
#include <iterator>
#include <string>
#include <type_traits>
#include <vector>
template<typename I> void CapitalizeAndOutputImpl(I first, I last) {
std::string t;
std::transform(first, last, std::back_inserter(t), std::toupper);
std::cout << t << " ";
}
template<typename T>
struct CapitalizeAndOutputHelper {
void operator()(const T& s) {
CapitalizeAndOutputImpl(std::begin(s), std::end(s));
}
};
template<typename T>
struct CapitalizeAndOutputHelper<T*> {
void operator()(const T* s) {
CapitalizeAndOutputImpl(s, s + std::strlen(s));
}
};
template<typename T> void CapitalizeAndOutput(T&& s) {
CapitalizeAndOutputHelper<std::remove_reference<T>::type>()(s);
std::cout << std::endl;
}
template<typename First, typename... Rest> void CapitalizeAndOutput(First&& first, Rest&&... rest) {
CapitalizeAndOutputHelper<std::remove_reference<First>::type>()(first);
CapitalizeAndOutput(rest...);
}
int main() {
std::string hello{ "string hello" };
const std::string world{ "const string world" };
char arrHello[] = "char[] hello";
const char vHelloInit[] = "char* hello";
std::vector<char> vHello(std::begin(vHelloInit), std::end(vHelloInit));
const char* cworld = "const char* world";
CapitalizeAndOutput(hello, world, arrHello, "literal world", vHello.data(), cworld);
}
到目前为止,最简单的是有两个重载:
void do_stuff() {}
template<class...Ts>
void do_stuff(std::string s, Ts&&... ts);
并第二次使用您现有的身体。
我们得到完美的转发,然后在您的变异和输出之前,我们复制。
如果你想让变异人传播出去,你可能错了。 如果你坚持,@Cassio的方法看起来不错。
- cppcheck在const std::string[]上引发警告
- 将std::string传递给WriteConsole API
- 为std::string的某个索引赋值
- 使用 std::string () const 函数启动线程或未来
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 如何更改大小(std::string)
- std::string 的对象真的可以移动吗?
- SegFault 同时使用 std::string::operator+= 和函数作为参数
- 无法从 std::string 中提取C++ Unicode 符号
- std::string 构造函数如何处理固定大小的 char[]?
- 真的没有来自 std::string_view 的 std::string 的显式构造函数吗?
- 将C++ std::string 转换为 UTF-16-LE 编码的字符串
- 重载 + 自己的类和 std::string 的运算符
- 如何使用 std::string 作为 QHash 的键?
- 将日语 wstring 转换为 std::string
- 可以从std::string继承以提供类型一致性吗
- 构造函数采用std::string_view与std::string并移动
- 在共享缓冲区内存中创建 ::std::string 对象
- std::string.size() 未知行为
- Valgrind 在 std::string::swap 中报告 SIGILL