7

I like vectors, and generally use them over arrays. For that reason I created a templated variadic function to initialize vectors (included below).

Header (.h):

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

Source (.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;
}

It works great for most types (e.g. int, double...), but not for strings---as the compiler interprets them as 'const char *', thus

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

gives me the error:

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

Is there any way to get the string arguments to be interpreted as strings without having to cast each one?

8
  • It wouldn't be a pretty solution, but maybe I could pass 'string' as an additional argument which would then automatically cast to each element? :/ Commented Aug 21, 2012 at 19:40
  • 2
    Is it too burdensome to tell the compiler which type you want rather than relying on deduction? initVector<std::string>(2,"string 1","string 2") for example. Commented Aug 21, 2012 at 19:48
  • 2
    In addition to your immediate problems, it's illegal to pass non-POD types as varargs. Commented Aug 21, 2012 at 19:51
  • @ahenderson: I believe the error message is not caused by that line, but by the fact that template argument deduction is trying to fit the type parameter T to 2 different types at the point where the function is called. Commented Aug 21, 2012 at 19:52
  • 1
    @bames53: You're correct, but that's only a problem for solutions that try to do the conversion to string before the arguments are passed to the function. If raw const char*s are passed (as in my suggested solution), we're still good. Commented Aug 21, 2012 at 19:57

2 Answers 2

4

Because the type of the constant "string one" is const char* and not std::string there needs to be a conversion. va_arg can not make this conversion, so we need a second template argument:

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")

Note that I made some changes, most notably change resize to reserve, to prevent unnecessary objects from being created.


You can also simply use this (no risk of having number of elements messed up, and type safe):

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

Or use C++11 initializer lists:

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

For fun I made this little thing that is even neater and safer, but doesn't generalize into an arbitrary amount of arguments. It works by overloading. Here are the first three overloads and example usage:

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");

A full header (for up to 100 arguments) can be downloaded here: https://gist.github.com/3419369 .

Sign up to request clarification or add additional context in comments.

12 Comments

Awesome answer @nightcracker; that worked like a charm. A couple of questions: 1) I didn't know about this C++11 initializer lists thing that you and j_random_hacker pointed out---do I need a compiler option to get that to work (it didn't as is). 2) I've always opted out of the 'sizeof(args)/sizeof(args[0])' approach because someone told me it was 'bad practice' to do so... not to mention its a little unaesthetic. Is that standard practice, none-the-less?
@zhermes: still working on a better answer. You can use the array answer perfectly fine, though, even better with the end function in this answer: stackoverflow.com/a/4268956/565635 . What compiler do you use? Then I can give you the flag.
Technically, the argument "string one" is not a const char*, but a const char [11] (If I counted correctly), and this answer is overcomplicating the problem... If you are going to pass the type as a template argument, there is no need to create a second type and have it deduced, as the compiler is able to convert from one to the other.
@nightcracker i686-apple-darwin11-llvm-g++-4.2 (I think that's the correct answer to your question.... <noob>)
@DavidRodríguez-dribeas: true, but when used in templates const char[11] gets converted to const char* - I didn' t bother with the technicalities :)
|
1

Try using 2 template type parameters instead:

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

Very often (e.g. for int, double etc.) T and U will be the same. But the difference with the new strategy is that we now allow them to be different, provided that there is an implicit conversion from U to T (such as from const char* to string). This should be safe in the sense that, if no implicit conversion exists, you will get a compile-time error.

BTW interesting strategy -- it never occurred to me that va_list etc. could be employed in this way! OTOH, I believe there is new machinery in C++11 that allows vectors etc. to be directly initialised from initialiser lists, similar to how you have always been able to initialise arrays in C like int a[] = { 3, 4, 5 };, so it may be better to go with that.

1 Comment

You don't even need to assign from an initializer_list. You can use the new initialization syntax to just write int a[]{ 3, 4, 5 };

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.