Inserting a variadic argument list into a vector?
Asked Answered
P

7

30

Forgive me if this has been answered already, as I couldn't find it...

Basically I have an object that needs to take a variadic argument list in it's constructor and store the arguments in a vector. How do I initialize a vector from a the arguments of a variadic constructor?

class GenericNode {
public:
    GenericNode(GenericNode*... inputs) {
            /* Something like... */
        // inputs_.push_back(inputs)...;
}
private:
    std::vector<GenericNode*> inputs_;
};
Paschasia answered 20/12, 2012 at 19:13 Comment(3)
There's some invalid syntax in your example. What specifically are you trying to ask?Crabstick
use std::initializer_list<GenericNode*>.Kleeman
Sorry. To clarify, how do I use the argument list to populate a std::vector? @MooingDuck, i'll look into std::initializer_list. Thanks.Paschasia
K
30

The best thing would be to use an initializer list

#include <initializer_list>
#include <vector>
class GenericNode {
public:
    GenericNode(std::initializer_list<GenericNode*> inputs) 
        :inputs_(inputs) {} //well that's easy
private:
    std::vector<GenericNode*> inputs_;
};
int main() {
    GenericNode* ptr;
    GenericNode node{ptr, ptr, ptr, ptr};
} //compilation at http://stacked-crooked.com/view?id=88ebac6a4490915fc4bc608765ba2b6c

The closest to what you already have, using C++11 is to use the vector's initializer_list:

    template<class ...Ts>
    GenericNode(Ts... inputs) 
        :inputs_{inputs...} {} //well that's easy too
    //compilation at http://stacked-crooked.com/view?id=2f7514b33401c51d33677bbff358f8ae

And here's a C++11 version with no initializer_lists at all. It's ugly, and complicated, and requires features missing from many compilers. Use the initializer list

template<class T>
using Alias = T;

class GenericNode {
public:
    template<class ...Ts>
    GenericNode(Ts... inputs) { //SFINAE might be appropriate
         using ptr = GenericNode*;
         Alias<char[]>{( //first part of magic unpacker
             inputs_.push_back(ptr(inputs))
             ,'0')...,'0'}; //second part of magic unpacker
    }
private:
    std::vector<GenericNode*> inputs_;
};
int main() {
    GenericNode* ptr;
    GenericNode node(ptr, ptr, ptr, ptr);
} //compilation at http://stacked-crooked.com/view?id=57c533692166fb222adf5f837891e1f9
//thanks to R. Martinho Fernandes for helping me get it to compile

Unrelated to everything, I don't know if those are owning pointers or not. If they are, use std::unique_ptr instead.

Kleeman answered 20/12, 2012 at 20:11 Comment(10)
Wouldn't template<typename... T> GenericNode(T*... inputs) :inputs_{ inputs... } { } be closer to what he already has? i'd still go with std::initializer_list<GenericNode*> though.Chinoiserie
@JonathanWakely: No idea why I never thought of that. Fixed.Kleeman
@MooingDuck, others: Thanks. Just what I needed.Paschasia
Aside from the answer itself, and aside from wether it is practical or not, I find your 3rd solution very clever. Is there a guide somewhere explaining the reason for the individual pieces? Specifically, why the need for Alias, and why the need for the last '0' value?Abutter
@mmocny: I actually copy-pasted it from another answer I'd seen of another SO answer somewhere. The alias is required to make a nameless temporary array, which makes it blatantly clear to any compiler that it doesn't actually need to make the array of chars. For each Ts it adds a '0' to the array. However, if you pass no inputs, that's the same as char t[] = {} which is invalid, so we tack on an extra '0' so there's always at least one element in the temporary array.Kleeman
@TomášZato: Seems harsh. I answered the question completely, the reader can trivially figure out the includes themselves. Regardless: added.Kleeman
Is the third solution guaranteed to preserve order?Edra
@Zelta: Honestly, I can't recall. I sort of recall that the ... is guaranteed to unpack in order, but I wouldn't swear it is so.Kleeman
@MooingDuck if I remember correctly, it is guaranteed since c++17. So here's a better solution for c++ < 17: https://mcmap.net/q/500357/-variadic-construction-for-initialising-vector-of-unique_ptr-to-base-typeEdra
And in c++17, there are fold expressions for this kind of tasks.Edra
C
10
    // inputs_.push_back(inputs)...;

This doesn't work because you can't expand a parameter pack as a statement, only in certain contexts such as a function argument list or initializer-list.

Also your constructor signature is wrong, if you're trying to write a variadic template it needs to be a template!

Once you write your constructor signature correctly the answer is easy, just construct the vector with the pack expansion:

#include <vector>

class GenericNode
{
public:
  template<typename... T>
    GenericNode(T*... inputs) : inputs_{ inputs... }
    { }
private:
    std::vector<GenericNode*> inputs_;
};

(You could instead have set it in the constructor body with:

inputs_ = { inputs... };

but the cool kids use member initializers not assignment in the constructor body.)

The downside of this solution is that the template constructor accepts any type of pointer arguments, but will then give an error when trying to construct the vector if the arguments aren't convertible to GenericNode*. You could constrain the template to only accept GenericNode pointers, but that's what happens automatically if you do what the other answers suggest and make the constructor take a std::initializer_list<GenericNode*>, and then you don't need any ugly enable_if SFINAE tricks.

Chinoiserie answered 20/12, 2012 at 20:55 Comment(0)
A
3

You can't use a variadic argument list unless it's a template, you can, as stated, use a initializer_list like this:

class GenericNode {
public:
    GenericNode(std::initializer_list<GenericNode*> inputs) : inputs_(inputs)
    {
    }
private:
    std::vector<GenericNode*> inputs_;
};

template <class ... T>
GenericNode* foo(T ... t)
{
    return new GenericNode({t...});
}
Arguseyed answered 20/12, 2012 at 20:6 Comment(2)
Why do you have the helper function?Kleeman
@MooingDuck Just to show the use of variadic template parametersArguseyed
C
2

Another way to do it:

#include <iostream>
#include <vector>

using std::vector;

template <typename T>
void variadic_vector_emplace(vector<T>&) {}

template <typename T, typename First, typename... Args>
void variadic_vector_emplace(vector<T>& v, First&& first, Args&&... args)
{
    v.emplace_back(std::forward<First>(first));
    variadic_vector_emplace(v, std::forward<Args>(args)...);
}

struct my_struct
{
    template <typename... Args>
    my_struct(Args&&... args)
    {
        variadic_vector_emplace(_data, std::forward<Args>(args)...);
    }

    vector<int>& data() { return _data; }

private:
  vector<int> _data;
};


int main()
{
    my_struct my(5, 6, 7, 8);

    for(int i : my.data())
      std::cout << i << std::endl;
}
Chessman answered 21/12, 2012 at 0:18 Comment(0)
N
2
class Blob
 {
    std::vector<std::string> _v;
 public:

    template<typename... Args>
    Blob(Args&&... args)
    : _v(std::forward<Args>(args)...)
    {  }

};

int main(void)
{
    const char * shapes[3] = { "Circle", "Triangle", "Square" };

    Blob b1(5, "C++ Truths"); 
    Blob b2(shapes, shapes+3);
}

Example from C++11 Truths looks simple enough...;) Not a complete solution but might give you some ideas.

Nita answered 29/10, 2013 at 2:11 Comment(1)
This works ok for your b1 and b2 examples, but the OP wants to do Blob b3{ "Circle", "Triangle", "Square" }; and your Blob constructor can't do that, because you use value-initialization for _v. If you change it to use list-initialization for _v it would support what the OP wants to do.Chinoiserie
C
1

Note: If the element-type of a vector is not copy-initializable (it is in OP post), the std::initializer list route will not work.

You can still use a variadic unpack statement (post C++ 17):

(inputs_.emplace_back(std::move(args)), ...);
Caterwaul answered 2/5, 2022 at 23:59 Comment(0)
C
0

I recently wrote the following function that takes a string with {1} , {2} , {3} ... in it and substitutes the argument list. I ran in to the same problem until I decided to let the compiler work it out for itself with the auto keyword.

#include <string>
#include <vector>

using std::string;
using std::vector;

template<typename S, typename... Args>
string interpolate( const S& orig , const Args&... args)
{
   string out(orig);

   auto va = {args...};
   vector<string> v{va};

   size_t i = 1;

   for( string s: v)
    {
      string is = std::to_string(i);
      string t = "{" +  is + "}";
      try
         {
           auto pos = out.find(t);
           if(pos != out.npos) 
              {
                 out.erase(pos, t.length());
                 out.insert( pos, s); 
              }                  
           i++;
         }
    catch( std::exception& e)
       {
          std::cerr << e.what() << std::endl;
       }
     } // for

   return out;
 }

Apparently that is good enough as long as the types line up correctly. In this case I am using only std::string throughout. I think this is an elegant technique, but it may have drawbacks.

Coelom answered 28/7, 2018 at 4:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.