1

I have the following template function:

struct ms {
    template <typename... Args>
    void update(string& query, Args&... args);
};

template <typename... Args>
void ms::update(string& query, Args&... args)
{
    const int size = sizeof...(args);
    vector<string> vec = { args... };
    for (int i = 0; i < size; ++i) {
        cout << query << ": " << vec[i] << endl;
    }
}

However, I would like to eliminate the use of template and simply make this a member function that takes one or more string arguments. All of the documentation and examples on Variadic Functions I can find show use of character arrays and doing a while pointer != null to grab each value in the array.

If I do something like:

void update(string& query, string&... args);

how would I iterate over the args parameters, if there are any?

user1324674
  • 384
  • 1
  • 3
  • 12
  • 1
    Why not just pass a container of strings, e.g. `std::vector`? – Cory Kramer Sep 01 '17 at 19:18
  • Take a `vector` directly ? – Jarod42 Sep 01 '17 at 19:19
  • https://stackoverflow.com/questions/3703658/specifying-one-type-for-all-arguments-passed-to-variadic-function-or-variadic-te – LogicStuff Sep 01 '17 at 19:19
  • 1
    What's wrong with the templated version actually? Well, you could use an ellipsis and `va_args` and that stuff, but I wouldn't really recommend that. – user0042 Sep 01 '17 at 19:19
  • If you don't need to take ownership of the strings, initializer_list is actually a much better solution than a vector of strings. – Nir Friedman Sep 01 '17 at 19:29
  • @user0042 we were instructed to minimize use of templates where possible due to overhead. @ others, the input parameters are stored in an array of structures which contain a multidimensional character array. Due to the way I've overloaded some operators, its simpler to call a function like: update(query, arg[column][row], arg[colum2][row]...) etc. If I were to do it by passing in a vector directly, then I would have to first create a vector out of all of these strings, then pass the vector which would ultimately be less efficient (or at least less clean). – user1324674 Sep 01 '17 at 19:31
  • @user1324674 What _overhead_ are you talking about? – user0042 Sep 01 '17 at 19:32
  • @user0042 code size? I don't know the difference between using a template vs variadic, though. Are they the same? – user1324674 Sep 01 '17 at 19:34
  • @user1324674 OK, if code size really is a critical path for you, then go with the good old ellipsis and [`va_arg`](http://en.cppreference.com/w/cpp/utility/variadic/va_arg). – user0042 Sep 01 '17 at 19:36
  • 2
    You can use initializer list `void update( std::initializer_list args)` – Artemy Vysotsky Sep 01 '17 at 19:38
  • 1
    No, do not use va_arg. Use vector, use initializer list, use templates, but not va_arg. It's a pile of hot type unsafe garage. – Nir Friedman Sep 01 '17 at 19:48
  • You can either use the templated variadic fucntion or old plain 'c' style variadic function with va_args. But in the latter case there is no info about neither parameter type nor about the number of parameters, unless you specifically provide them (as a format string in printf). The other method is to overload the function with all possible number of parameters (if you know it). – Serge Sep 01 '17 at 20:01

1 Answers1

1

This is an array_view<T>:

template<class T>
struct array_view;
template<class D>
struct array_view_base;

template<class T>
struct array_view_base<array_view<T>> {
  T* b=0; T* e=0;
  T* begin() const { return b; }
  T* end() const { return e; }
  T& operator[](std::size_t i)const{ return begin()[i]; }
  std::size_t size() const { return end()-begin(); }
  T& front() const { return *begin(); }
  T& back() const { return *(end()-1); }
  array_view<T> without_front( std::size_t N=1 ) const {
    N=(std::min)(N, size());
    return {begin()+N, end()};
  }
  array_view<T> without_back( std::size_t N=1 ) const {
    N=(std::min)(N, size());
    return {begin(), end()-N};
  }

  array_view_base( T* s, T* f ):b(s),e(f){}
  array_view_base( T* s, std::size_t sz ):array_view_base(s, s+sz) {}

  template<std::size_t N>
  array_view_base( T(&arr)[N] ):array_view_base(arr, N) {}
  template<class C,
    std::enable_if_t<!std::is_same<std::decay_t<C>, array_view<T>>{}, int> =0
  >
  array_view_base( C&& c ):array_view_base(c.data(), c.size()) {}
};

template<class T>
struct array_view:array_view_base<array_view<T>> {
  using array_view_base<array_view<T>>::array_view_base;
};
template<class T>
struct array_view<T const>:array_view_base<array_view<T const>> {
  using array_view_base<array_view<T const>>::array_view_base;
  array_view( std::initializer_list<T> il ):array_view( std::addressof(*il.begin()), il.size() ) {}
};

it works a bit like a gsl::span<T>. it is a contiguous range of T.

Unlike gsl::span<T>, array_view<T const> can be constructed from an initializer_list<T>.

With it your code should look like:

struct ms {
  void update(string const& query, array_view<string const> args);
};
void ms::update(string const& query, array_view<string const> args)
{
  for (int i = 0; i < args.size(); ++i) {
    cout << query << ": " << args[i] << endl;
  }
}

and you call it like:

ms{}.update( "Hello", {"one", "two", "three"} );

Live example.


As a worse example, simply take a std::vector<std::string>.

At point of call

ms{}.update( "Hello", {"one", "two", "three"} );

also works. Unlike my solution, this causes a memory allocation.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524