如何编写一个可变参数模板,它将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 :: conditional? 也许我需要通过在模板中创建一个类型为auto&amp;&amp;&amp;的局部变量来解决它。到目前为止,我在尝试过的各种各样的事情上都没有取得任何成功。
答案 0 :(得分:4)
(1)无法编译此测试用例
std::string hello = "hello";
const std::string earth = "earth";
Capitalize_And_Output(hello, "planet", earth);
因为earth
是const std::string
并且没有超载可以接受此调用。 (试试吧!)
(2)它无法编译可转换为const char*
的类型(std::string
除外),例如,
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的解决方案的评论中的要求)。
第二次超载意味着采取其他一切,即rvalues和const
左值。我们的想法是将参数复制到左值,然后传递给第一个重载。这个函数当然可以用这种方式实现(仍然不考虑第二个参数):
void Capitalize_And_Output_impl(const std::string& str) {
std::string tmp(str);
Capitalize_And_Output_impl(tmp);
}
这项工作视需要而定。然而,Dave Abrahams的着名article解释说,当您通过引用const
并将其复制到函数内部(如上所述)时,最好是按值进行参数(因为在某些情况下)环境编译器可能会避免副本)。总之,这种实现更可取:
void Capitalize_And_Output_impl(std::string str) {
Capitalize_And_Output_impl(str);
}
不幸的是,对于第一次重载,对左值的Capitalize_And_Output_impl
的调用也可以指向此重载。这产生了编译器抱怨的歧义。这就是为什么我们需要第二个论点。
第一次重载需要int
,第二次需要long
。因此,传递文字0
(int
)会使第一次重载超过第二次,但只有在出现歧义时才会这样。在其他情况下,即,当第一个参数是rvalue或const
左值时,不能使用第一个重载,而第二个参数可以在文字0
被提升为long
之后。< / p>
两个最后的评论。 (1)如果你想避免Capitalize_And_Output
中的递归调用(我想这只是一个品味问题),那么你可以使用与Simple的解决方案相同的技巧(通过unpack
)和( 2)我没有看到需要传递lambda wrap ::toupper
,就像Simple的解决方案一样。
答案 1 :(得分:2)
您不需要类型特征:
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;
}
答案 2 :(得分:1)
此版本不会对参数进行不必要的复制,不会引入任何不必要的临时字符串,并且避免为编译时长度已知的文字字符串调用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);
}
答案 3 :(得分:0)
到目前为止最容易的是有两个重载:
void do_stuff() {}
template<class...Ts>
void do_stuff(std::string s, Ts&&... ts);
并使用现有的身体进行第二次。
我们得到完美的转发,然后在你的变异和输出之前,我们复制。
如果你想让mutate传播,你可能错了。如果你坚持,@ Cassio的方法看起来不错。