如何为 std::string 可变模板参数调用 c_str()?
How to invoke c_str() for std::string variadic template parameters?
我有一个接受格式字符串 + 参数的方法(就像 printf() 一样),但是,我为此目的使用了可变参数模板:
template<typename... Args>
static void log(const char* pszFmt, Args&&... args)
{
doSomething(pszFmt, std::forward<Args>(args)...);
}
一些参数可以是 std::string 个实例。是否有可能确保 doSomething 永远不会接受 std::string,但会始终接受 const char*
而不是传递给 log()
的每个来源 std::string
?
换句话说,我需要一种方法将所有 args
转发到 doSomething()
,使所有 std::string
参数替换为 std::string::c_str()
returns .
提前致谢!
您可以定义自己的 "forwarding" 方法:
template<typename T>
decltype(auto) myForward(T&& t)
{
return t;
}
template<>
decltype(auto) myForward(std::string& t)
{
return t.c_str();
}
template<>
decltype(auto) myForward(std::string&& t)
{
return t.c_str();
}
template<typename... Args>
static void log(const char* pszFmt, Args&&... args)
{
doSomething(pszFmt, myForward<Args>(std::forward<Args>(args))...);
}
C++17版本
您可以使用 SFINAE 来实现:
#include <iostream>
#include <utility>
#include <string>
template <typename, typename = void>
struct has_c_str : std::false_type {};
template <typename T>
struct has_c_str<T, std::void_t<decltype(&T::c_str)>> : std::is_same<char const*, decltype(std::declval<T>().c_str())>
{};
template <typename StringType,
typename std::enable_if<has_c_str<StringType>::value, StringType>::type* = nullptr>
static void log(const char* pszFmt, StringType const& arg) {
std::cout << "std::string version" << std::endl;
}
template <typename StringType,
typename std::enable_if<!has_c_str<StringType>::value, StringType>::type* = nullptr>
static void log(const char* pszFmt, StringType arg) {
std::cout << "const char * version" << std::endl;
}
template <typename... Args>
static void log(const char* pszFmt, Args&&... args) {
log(pszFmt, std::forward<Args>(args)...);
}
int main() {
log("str", std::string("aa")); // output: std::string version
log("str", "aa"); // output: const char * version
return 0;
}
完整演示here
这是一个替代解决方案。如果您的记录器只是打印每个参数而不是 "store" 它,那么就没有必要完美转发参数,一个简单的按引用传递就足够了。
在那种情况下,您可以简单地为各种 "printable" 类型重载或专门化打印机函数。
template <class T>
decltype(auto) printer(T const& t) {
return t;
}
inline const char* printer(std::string const& t) {
return t.c_str();
}
template<typename... Args>
void log(const char* pszFmt, Args const&... args) {
printf(pszFmt, printer(args)...);
}
int main() {
std::string str{"xyz"};
log("%s %s %s\n", "abc", std::string("def"), str);
}
注意:在重载决策期间,非模板重载将始终是首选。
我有一个接受格式字符串 + 参数的方法(就像 printf() 一样),但是,我为此目的使用了可变参数模板:
template<typename... Args>
static void log(const char* pszFmt, Args&&... args)
{
doSomething(pszFmt, std::forward<Args>(args)...);
}
一些参数可以是 std::string 个实例。是否有可能确保 doSomething 永远不会接受 std::string,但会始终接受 const char*
而不是传递给 log()
的每个来源 std::string
?
换句话说,我需要一种方法将所有 args
转发到 doSomething()
,使所有 std::string
参数替换为 std::string::c_str()
returns .
提前致谢!
您可以定义自己的 "forwarding" 方法:
template<typename T>
decltype(auto) myForward(T&& t)
{
return t;
}
template<>
decltype(auto) myForward(std::string& t)
{
return t.c_str();
}
template<>
decltype(auto) myForward(std::string&& t)
{
return t.c_str();
}
template<typename... Args>
static void log(const char* pszFmt, Args&&... args)
{
doSomething(pszFmt, myForward<Args>(std::forward<Args>(args))...);
}
C++17版本
您可以使用 SFINAE 来实现:
#include <iostream>
#include <utility>
#include <string>
template <typename, typename = void>
struct has_c_str : std::false_type {};
template <typename T>
struct has_c_str<T, std::void_t<decltype(&T::c_str)>> : std::is_same<char const*, decltype(std::declval<T>().c_str())>
{};
template <typename StringType,
typename std::enable_if<has_c_str<StringType>::value, StringType>::type* = nullptr>
static void log(const char* pszFmt, StringType const& arg) {
std::cout << "std::string version" << std::endl;
}
template <typename StringType,
typename std::enable_if<!has_c_str<StringType>::value, StringType>::type* = nullptr>
static void log(const char* pszFmt, StringType arg) {
std::cout << "const char * version" << std::endl;
}
template <typename... Args>
static void log(const char* pszFmt, Args&&... args) {
log(pszFmt, std::forward<Args>(args)...);
}
int main() {
log("str", std::string("aa")); // output: std::string version
log("str", "aa"); // output: const char * version
return 0;
}
完整演示here
这是一个替代解决方案。如果您的记录器只是打印每个参数而不是 "store" 它,那么就没有必要完美转发参数,一个简单的按引用传递就足够了。
在那种情况下,您可以简单地为各种 "printable" 类型重载或专门化打印机函数。
template <class T>
decltype(auto) printer(T const& t) {
return t;
}
inline const char* printer(std::string const& t) {
return t.c_str();
}
template<typename... Args>
void log(const char* pszFmt, Args const&... args) {
printf(pszFmt, printer(args)...);
}
int main() {
std::string str{"xyz"};
log("%s %s %s\n", "abc", std::string("def"), str);
}
注意:在重载决策期间,非模板重载将始终是首选。