unique_ptr
unique_ptr

Reputation: 253

Wrapping ellipsis-based function with variadic template function

I am trying to wrap a function 'prepared(.)' from an external library (libpqxx). Said function accepts a variable number of arguments. This is achieved by using multiple operator() in succession. One operator() pr. argument, like so:

pqxx::connection connection(connection_str);
connection.prepare("update_person", "UPDATE person SET name = $1 WHERE id = $2 AND version = $3");
pqxx::work w(connection);

// The following executes prepared statement.
// The number of arguments is variable. Notice
// the syntax with multiple () in succession...
w.prepared("update_person")("Jack")(1)(0).exec();

I am trying to wrap the last function by using a variadic template function, like so:

template<typename... T>
pqxx::result exec_prepared(const std::string& name, const T&... args) const {
    return w.prepared(name)(args...).exec();
}

...but it does not work. The code compiles, but I get a runtime error saying that the number of arguments do not match the expected number of arguments given the prepared-sql-statement.

Could someone please clarify how wrapping of this type of function is done using variadic template? Thanks!

Upvotes: 2

Views: 372

Answers (3)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275800

I would do this with a fold.

template<class F>
struct invoke_by_times_t;
template<class F>
invoke_by_times_t<F> invoke_by_times(F&&)

template<class F>
struct invoke_by_times_t {
  F f;
  template<class Rhs>
  auto operator*(Rhs&&rhs)&&{
    return invoke_by_times( std::forward<F>(f)(std::forward<Rhs>(rhs)) );
  }
};

which lets us do this:

w.prepared("update_person")("Jack")(1)(0).exec();

with

(invoke_by_times([&](auto&&x){ return w.prepared(x); })*("update_person")*("Jack")*(1)*(0)).f.exec();

now that isn't that useful, but with C++17 that is a simple fold on args!

In , we can write product:

template<class A1, class A2>
delctype(auto) product(A1&& a1, A2&& a2){
  return std::forward<A1>(a1)*std::forward<A2>(a2);
}
template<class A1, class A2, class...Args>
delctype(auto) product(A1&& a1, A2&& a2, Args&&...args){
  return product(std::forward<A1>(a1)*std::forward<A2>(a2), std::forward<Args>(args)...);
}

giving us:

product(invoke_by_times([&](auto&&x){ return w.prepared(x); }), "update_person", "Jack", 1, 0).f.exec();

Upvotes: 0

max66
max66

Reputation: 66230

Aschepler explained you what's wrong with your actual code.

Not sure to understand how the libpqxx library works but I can try an educated guess and propose a quite simple solution.

What about something like the following code (sorry: untested) ? (and corrected by aschepler; thanks!)

template <typename ... Ts>
pqxx::result exec_prepared (std::string const & name,
                            Ts const & ... args) const
 {
   using unused = int[];

   auto p = w.prepared(name);

   (void)unused { 0, (p = p(args), 0)... };

   return p.exec();
 }

If you can use C++17, I suppose you can simplify (avoiding the unused trick) as follows (sorry again: not tested)

pqxx::result exec_prepared (std::string const & name,
                            Ts const & ... args) const
 {
   auto p = w.prepared(name);

   (p = p(args), ...);

   return p.exec();
 }

Upvotes: 0

aschepler
aschepler

Reputation: 72431

If the pack args represents three parameters, then

return w.prepared(name)(args...).exec();

expands to

return w.prepared(name)(args0, args1, args2).exec();

which is different from the

return w.prepared(name)(args0)(args1)(args2).exec();

which you need.

It looks like you'll need a helper function which recursively applies one argument at a time:

private:
    template <typename E>
    static E&& apply_prepared_args(E&& expr) {
        return std::forward<E>(expr);
    }
    template <typename E, typename First, typename... Rest>
    static decltype(auto) apply_prepared_args(
        E&& expr, const First& first, const Rest& ...rest) {
        return apply_prepared_args(std::forward<E>(expr)(first), rest...);
    }

public:
    template<typename... T>
    pqxx::result exec_prepared(
        const std::string& name, const T&... args) const {
        return apply_prepared_args(w.prepared(name), args...).exec();
    }

Upvotes: 2

Related Questions