Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++11 variable argument alignment

This is the interface I am trying to achieve:

 Statement  select("SELECT * FROM People WHERE ID > ? AND ID < ?");
 select.execute(1462, 1477, [](int ID, std::string const& person, double item1, float item2){
     std::cout << "Got Row:" 
               << ID     << ", " 
               << person << ", " 
               << item1  << ", " 
               << item2  << "\n";
 });

Where the '?' in the select string are matched against the variable argument list 1462, 1477 at runtime.

This is the class definition:

class Statement
{
    public:
        Statement(std::string const&);

        template<class Action, class ...Args>
        void execute(Args... param, Action action);
};

Unfortunately this generates an error:

test.cpp:133:12: error: no matching member function for call to 'execute'
select.execute(1462, 1477, [](int ID, std::string const& person, double item1, float item2){
~~~~~^~~~~~~

test.cpp:86:14: note: candidate template ignored: couldn't infer template argument 'Action'
void execute(Args... param, Action action)
~~~^~~~~~~

1 error generated.

But if I change the function definition slightly (below) it compiles fine.

class Statement
{
    public:
        Statement(std::string const&);

        template<class Action, class ...Args>
        void execute(Action action, Args... param);
                  // ^^^^^^^ Move action to the front.
};
// Also changed the call by moving the lambda to the first argument.

I know its some syntactic sugar where the variable argument list goes, but I would like to put the variable argument list first. Are there any tricks I can use to help the compiler deduce the var arg list correctly?

like image 810
Martin York Avatar asked Oct 20 '22 11:10

Martin York


1 Answers

It's a bit ugly, but you could use tuples:

#include <iostream>
#include <string>
#include <tuple>

template<int... Is>
struct integer_sequence {};
template<int N, int... Is>
struct make_integer_sequence : make_integer_sequence<N-1, N-1, Is...> {};
template<int... Is>
struct make_integer_sequence<0, Is...> : integer_sequence<Is...> {};

class Statement
{
    private:
        std::string foo;

    public:
        Statement(std::string const& p)
            : foo(p)
        {}

        template<class ...Args>
        void execute(Args... param)
        {
            execute_impl(make_integer_sequence<sizeof...(Args)-1>{}, param...);
        }

        template<int... Is, class... Args>
        void execute_impl(integer_sequence<Is...>, Args... param)
        {
            std::get<sizeof...(Args)-1>(std::tie(param...))
                (std::get<Is>(std::tie(param...))..., foo);
        }
};

Usage example:

int main()
{
    Statement s("world");
    s.execute("hello", ", ",
              [](std::string const& p1, std::string const& p2,
                 std::string const& p3)
              { std::cout << p1 << p2 << p3; });
    std::cout << "\nEND\n";
}

Here's an alternative solution, a bit less ugly but more verbose:

#include <iostream>
#include <string>
#include <tuple>

template<class Tuple, class...>
struct pop_back;

template<class T, class... Ts, class... Us>
struct pop_back<std::tuple<T, Ts...>, Us...>
    : pop_back<std::tuple<Ts...>, Us..., T>
{};

template<class T, class... Us>
struct pop_back<std::tuple<T>, Us...>
{
    using type = std::tuple<Us...>;
};

class Statement
{
    private:
        std::string foo;

    public:
        Statement(std::string const& p)
            : foo(p)
        {}

        template<class ...Args>
        void execute(Args... param)
        {
            helper<typename pop_back<std::tuple<Args...>>::type>
                ::execute(param..., foo);
        }

        template<class T>
        struct helper;

        template<class... Args>
        struct helper< std::tuple<Args...> >
        {
            template<class Action>
            static void execute(Args... param, Action action, std::string foo)
            {
                action(param..., foo);
            }
        };
};

Here's a short is_callable trait that allows a static_assert for nicer error messages:

template<class F, class... Args>
struct is_callable
{
    template<class F1>
    static auto test(int)
        -> decltype( std::declval<F1>() (std::declval<Args>()...),
                     std::true_type{} );

    template<class F1>
    static std::false_type test(...);

    constexpr static auto value = decltype(test<F>(0))::value;
};

For example:

template<int... Is, class... Args>
void execute_impl(integer_sequence<Is...>, Args... param)
{
    auto& action = std::get<sizeof...(Args)-1>(std::tie(param...));
    auto param_tuple = std::tie(param...);

    static_assert(is_callable<decltype(action),
                              typename std::tuple_element<Is,
                                              decltype(param_tuple)>::type...,
                              decltype(foo)>::value,
                  "The action is not callable with those argument types.");

    action(std::get<Is>(param_tuple)..., foo);
}
like image 65
dyp Avatar answered Oct 23 '22 03:10

dyp