输入traits来区分const char [N]和std :: string?

时间:2013-10-28 16:04:23

标签: c++ templates c++11 auto variadic-templates

如何编写一个可变参数模板,它将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;的局部变量来解决它。到目前为止,我在尝试过的各种各样的事情上都没有取得任何成功。

4 个答案:

答案 0 :(得分:4)

我发现Simplesolution有两个问题:

(1)无法编译此测试用例

std::string hello = "hello";
const std::string earth = "earth";
Capitalize_And_Output(hello, "planet", earth);

因为earthconst 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。因此,传递文字0int)会使第一次重载超过第二次,但只有在出现歧义时才会这样。在其他情况下,即,当第一个参数是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的方法看起来不错。

相关问题