函数模板参数推导指南?

15

我正在尝试编写一些模板函数,可以接受std::basic_string或char数组,从中构建出basic_string

我的当前解决方案是:

#include <string>

template<typename CharT>
void foo(std::basic_string<CharT> str)
{
    (void)str; // do something with str
}
template<typename CharT>
void foo(CharT const * arr)
{
    return foo(std::basic_string<CharT>{arr});
}

int main(void)
{
    foo("hello");
    foo(std::string{ "hello" });
    foo(L"hello");
    foo(std::wstring{ L"hello" });
}

但这意味着对于每个函数,我都需要编写另一个调用第一个函数的函数。这很烦人;有没有更简单的方法?也许可以使用模板推导指南,但据我所知,它不存在于函数中,只存在于类中。

第一个模板函数不足够,因为模板推导失败:编译器无法从CharT const *中推导出std::basic_string<CharT>中的CharT。这就是为什么我需要一种更简单的方法来告诉编译器。


2
用单一函数替换这两个重载:template<typename CharT> void foo(std::basic_string_view<CharT> str)? - ildjarn
1
@ildjarn 同样的问题,模板推导失败。 - Boiethios
当调用 CharT const * 版本时,我只想构建一个 basic_string 并像示例中那样调用第一个版本。当这不是一个模板函数时,一切都是隐式的,但在这里编译器不会从 char const * 构建一个 basic_string <char>,因为推导失败了。 - Boiethios
2个回答

11

硬着头皮用两个重载函数。任何聪明的解决方案(如davidhigh所展示的)只会给下一个读者增加不必要的复杂性、潜在的错误和困惑。

你只写一次但读多次。写一个1行代码的额外重载是值得做的小不便,而不是使用不太习惯的、晦涩难懂的聪明方法

别误会,我喜欢在C++中找到这些聪明的解决方案,但如果我在生产代码中找到了这个解决方案,那么我需要花费几分钟时间才能弄清楚它是什么,它做了什么,结果只发现它只是以复杂的方式完成了应该是非常基本的事情,我会…好吧,让我们说我不会对代码作者说好话。当编写代码时变懒将会让你在维护、调试、扩展甚至使用代码时花费更多的时间。

写简单、通俗易懂的代码!


1
我认为这是最佳生产解决方案,但我接受了其他答案,因为它回答了这个问题。 - Boiethios

8

查阅了更多资料后,我认为最好的选择是使用C++17特性std::basic_string_view

template<typename CharT>
void foo(std::basic_string_view<CharT> str)
{
    (void)str; // do something with str ...
               // while remembering that string_view does not own the string
}

如果您可以使用C++17编译器,请忘记以下旧版说明。



这里有两种情况需要考虑。第一种情况是,您并不真正想对基本字符串做特殊处理,而是只应用于 char 数组也可用的方法(并且只是想确保无论参数如何都可以正确调用它)。在这种情况下,我会简单地使用通用模板参数:

template<typename string_type
        /* possibly some SFINAE to allow/disallow certain types */>
auto foo(string_type s)
{
    std::cout << s << std::endl;
}

第二种情况是您想对字符串进行一些特殊操作,而这些操作在字符数组中不存在。在这种情况下,您需要一个basic_string的重载,但您可能只想编写一次,而不是为每个使用的函数都编写一次。以下string_invoker类尝试实现这一点(但仍需要改进,正在努力中):

template<typename method>
struct string_invoker_impl
{
    string_invoker_impl(method m) : m(m) {}

    template<typename CharT>
    auto operator()(std::basic_string<CharT> str) const
    {
        return m(str);
    }

    template<typename CharT>
    auto operator()(CharT const * arr) const
    {
        return operator()(std::basic_string<CharT>{arr});
    }

    //possibly further methods for non-const array's, modification, etc.    

    method m;
};

auto string_invoker = [](auto m) { return string_invoker_impl<decltype(m)>{m}; };

auto foo_impl = [](auto str) {std::cout<< str <<std::endl; };
auto foo = string_invoker(foo_impl);

//you  can merge the previous two calls also in a single one:
//auto foo = string_invoker( [](auto str) {std::cout<< str <<std::endl; });


int main(void)
{
    foo("hello");
    foo(std::string{ "hello" });
    //foo(L"hello");                      //need std::wcout, thus it fails with std::cout
                                          //but it's no general problem, just overload your foo_impl function
    //foo(std::wstring{ L"hello" });
}

DEMO


一个可能的改进是将指针重载限制为char类型(也许通过使用std::char_traits进行SFINAE来实现?) - Rerito
1
@Rerito:可能是这样,但我认为std::basic_string的构造肯定会失败。 - davidhigh
哦,是的,确实因为它使用了std::char_traits<CharT>,我的错。 - Rerito
@Boiethios:请考虑更新我的答案。 - davidhigh
你的回答更新并没有起作用。具体来说,如果你使用了 const char* 来调用 foo,它就无法编译通过,而这恰好是提问者试图实现的目标。 - Cody Gray
@CodyGray:我在代码框中省略了#include语句,但这些在演示中已经拼写出来了。否则——你确定吗?请参见此处,特别是对于const char*部分。那是你的观点吗? - davidhigh

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