如何为std::string变长模板参数调用c_str()函数?

4

我有一个方法,接受格式字符串和参数(类似于 printf()),但是我使用可变模板来实现这个目的:

template<typename... Args>
static void log(const char* pszFmt, Args&&... args)
{
    doSomething(pszFmt, std::forward<Args>(args)...);
}

一些args可以是std::string实例。有没有可能确保doSomething永远不会接受std::string,而始终接受将每个源std::string传递给log()的const char*呢?

换句话说,我需要一种方法将所有args转发到doSomething(),使得所有std::string参数都替换为std::string::c_str()返回的内容。

提前致谢!

3个回答

3
你可以定义自己的“转发”方法:
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))...);
}

1
你仍然需要使用std::forward来转发args,否则它们会变成左值引用。 - rustyx
1
返回 auto 将总是按值返回。最好返回 T&& - super
你们两个都是对的,我需要使用 decltype(auto)std::forward,我已经编辑了我的回答。 - Michael
你需要为 myForward(T&&) 准备两个重载,而不是 string 版本。只需要通过 const& 接受 string 即可。 - rustyx

2
这里有一种替代方案。如果您的记录器只是打印每个参数而不是“存储”它,那么没有必要完美地转发参数,简单的引用传递就足够了。
在这种情况下,您可以为各种“可打印”类型简单地重载或专门化打印机函数。
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);
}

注意:在重载决议期间,非模板重载将始终被优先选择。

也是一个非常棒和简洁的解决方案。谢谢! - Roman

1

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;
}

完整演示 这里


1
我想接受所有三个答案,因为它们都解决了任务并具有自己的优点。然而,由于简单的答案更适合我的需求,我会接受它,但SFINAE应用程序也很棒。 - Roman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接