使用模板变量作为字符串而不是const char *

7

我喜欢向量,并且通常使用它们而不是数组。因此,我创建了一个模板化的可变参数函数来初始化向量(包含如下)。

头文件(.h):

template <typename T>
vector<T> initVector(const int argCount, T first, ...);

源文件(.hpp):

template <typename T>
vector<T> initVector(const int argCount, T first, ...) {
    vector<T> retVec;
    retVec.resize(argCount);

    if(argCount < 1) { ... }

    retVec[0] = first;

    va_list valist;
    va_start(valist, first);
    for(int i = 0; i < argCount-1; i++) { retVec[i+1] = va_arg(valist, T); }
    va_end(valist);

    return retVec;
}

它可以很好地处理大多数类型(例如int,double...),但不适用于字符串----因为编译器将它们解释为'const char *'。

vector<string> strvec = initVector(2, "string one", "string two");

出现错误:

error: conversion from ‘std::vector<const char*, std::allocator<const char*> >’ to non-scalar type ‘std::vector<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >’ requested

有没有办法让字符串参数被解释为字符串而不必每个都进行转换?

这可能不是一个很好的解决方案,但也许我可以将“字符串”作为附加参数传递,然后自动转换为每个元素? :/ - DilithiumMatrix
2
你觉得告诉编译器你需要哪种类型比依赖推断更加繁琐吗?例如:initVector<std::string>(2,"string 1","string 2") - tmpearce
2
除了你当前的问题外,将非POD类型作为可变参数传递是非法的。 - bames53
1
@bames53:你说得没错,但这只是对那些试图在函数参数传递之前将转换为“字符串”的解决方案存在问题。如果传递了原始的'const char *'(如我建议的解决方案),我们仍然很好。 - j_random_hacker
@tmpearce 这个解决方案失败了,因为可变参数无法接受非POD类型,正如barnes53所指出的那样。 - DilithiumMatrix
显示剩余3条评论
2个回答

4

由于常量"string one"的类型是const char*而不是std::string,因此需要进行转换。 va_arg无法进行这种转换,因此我们需要第二个模板参数:

template <typename VecT, typename EleT>
std::vector<VecT> init_vector(const size_t nargs, EleT first, ...) {
    std::vector<VecT> result;
    result.reserve(nargs);

    if (nargs == 0) {
        return result;
    }

    result.push_back(first);

    if (nargs == 1) {
        return result;
    }

    va_list valist;
    va_start(valist, first);

    for (int i = 1; i < nargs; ++i) {
        result.push_back(VecT(va_arg(valist, EleT)));
    }

    va_end(valist);

    return result;
}

std::vector<std::string> = init_vector<std::string>(2, "string one", "string two")

请注意,我进行了一些更改,最重要的是将 resize 更改为 reserve,以防止创建不必要的对象。


你也可以简单地使用以下方法(没有使元素数量混乱的风险,并且类型安全):

const char *args[] = {"string one" , "string two"};
std::vector<std::string> strvec(args, args + sizeof(args)/sizeof(args[0]))

您可以使用C++11初始化列表:

std::vector<std::string> strvec = {"string one" , "string two"};

我为了好玩做了这个小东西,它更加整洁和安全,但不能推广到任意数量的参数。它通过函数重载来实现。以下是前三个函数重载和示例用法:

template<class C>
inline C init_container() {
    return C();
}

template<class C, class T>
inline C init_container(T arg0) {
    const T args[1] = {arg0};
    return C(args, args + 1);
}

template<class C, class T>
inline C init_container(T arg0, T arg1) {
    const T args[2] = {arg0, arg1};
    return C(args, args + 2);
}

std::vector<std::string> vec =
    init_container< std::vector<std::string> >("hello", "world");

可以在此处下载完整的标题(最多100个参数):https://gist.github.com/3419369


@zhermes:仍在努力提供更好的答案。您可以完全使用数组答案,但是在此答案中使用end函数会更好:https://dev59.com/a2855IYBdhLWcg3wj1MS#4268956 。您使用哪个编译器?然后我可以给您提供标志。 - orlp
从技术上讲,参数“string one”不是const char*,而是const char [11](如果我数对了的话),这个答案过于复杂化了问题... 如果你要将类型作为模板参数传递,就没有必要创建第二个类型并使其被推导出来,因为编译器能够从一个类型转换为另一个类型。 - David Rodríguez - dribeas
@nightcracker i686-apple-darwin11-llvm-g++-4.2(我认为这是你问题的正确答案...<新手>) - DilithiumMatrix
@DavidRodríguez-dribeas: 是的,不过在模板中使用时,const char[11] 会被转换为 const char*——我没有深究技术细节 :) - orlp
看起来需要一个更新版本的g++(gcc),感谢所有的帮助。 - DilithiumMatrix
显示剩余7条评论

1

尝试使用2个模板类型参数:

template <typename T, typename U>
vector<T> initVector(const int argCount, U first, ...) {

很多时候(例如对于intdouble等),TU将是相同的。但新策略的不同之处在于,我们现在允许它们不同,只要从UT存在隐式转换(例如从const char*string)。这应该是安全的,因为如果不存在隐式转换,您将得到编译时错误。
顺便说一下,有趣的策略——我从未想过可以以这种方式使用va_list等!另一方面,我相信C++11中有新的机制,允许向量等直接从初始化器列表初始化,类似于您一直能够像int a[] = {3,4,5};这样初始化数组,因此最好采用这种方法。

你甚至不需要从initializer_list中进行赋值。你可以使用新的初始化语法,只需编写 int a[]{ 3, 4, 5 }; - emsr

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