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?

12
  • 1
    Why not just pass a container of strings, e.g. std::vector<std::string>? Commented Sep 1, 2017 at 19:18
  • Take a vector<std::string> directly ? Commented Sep 1, 2017 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. Commented Sep 1, 2017 at 19:19
  • 2
    You can use initializer list void update( std::initializer_list<std::string> args) Commented Sep 1, 2017 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. Commented Sep 1, 2017 at 19:48

1 Answer 1

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.

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

Comments

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.