一些上下文:我有看起来像这样的代码(这里指出了次要问题):

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";
 });


无论如何,这将连接到MySQL DB并开始从服务器提取数据。因此,在执行过程中,我循环执行结果并为每行调用lambda:

    template<typename Action, typename ...Args>
    void execute(Args... param, Action action)
    {
        // STUFF TO SET up connection.
        // Start retrieving rows.


        while(row = results->getNextRow())
        {
            call(action, row);
        }
    }


因此,此行从与mysql的套接字连接中获取一行(因此它调用Lambda接收每一行时(不先将这些行拉入内存)。因此,我要查看的代码是提取数据并调用lambda。

 // Statement::call

    template<typename Action>
    void call(Action action, std::unique_ptr<ResultSetRow>& row)
    {
        typedef CallerTraits<decltype(action)>   trait;
        typedef typename trait::AllArgs         AllArgs;
        Caller<trait::size, 0, AllArgs, Action>::call(action, row);
    }


这利用辅助类CallerTraitsCaller从流中提取所需的行,然后调用lambda:

// CallerTraits
// Get information about the arguments in the lambda

template <typename T>
struct CallerTraits
    : public CallerTraits<decltype(&T::operator())>
{};

template<typename C, typename ...Args>
struct CallerTraits<void (C::*)(Args...) const>
{
    static const int                        size = sizeof...(Args);
    typedef std::tuple<Args...>             AllArgs;
};


然后Caller

// Caller::call()
//    Reads the next argument required by the lambda from the stream.
//    An exception will be generated if the next argument on the stream
//    does not match the type expected by the lambda.
template<int size, int index, typename ArgumentTupple, typename Action, typename ...Args>
struct Caller
{
    static void call(Action action, std::unique_ptr<ResultSetRow>& row, Args... args)
    {
        // Get the next argument type required by the lambda.
        // As defined by index. Then remove all ref and const
        // bindings.
        typedef typename std::tuple_element<index, ArgumentTupple>::type    NextArgBase;
        typedef typename std::remove_reference<NextArgBase>::type           NextArgCont;
        typedef typename std::remove_const<NextArgCont>::type               NextArg;

        // Read the next value from the stream.
        NextArg val;
        row->getValue(val);

        // Recursively call Caller::call() (via doCall())
        // To get the next argument we need. All the arguments
        // are accumulated in the var args parameter `args`
        doCall<size-1, index+1, ArgumentTupple>(action, row, args..., val);
    }
};


不需要更多参数的情况下进行专业化可以检索:

// Specialization of Caller::call() when we have got all the arguments.
// This simply calls the lambda with the arguments we have accumulated.
template<int index, typename ArgumentTupple, typename Action, typename ...Args>
struct Caller<0, index, ArgumentTupple, Action, Args...>
{
    static void call(Action action, std::unique_ptr<ResultSetRow>&, Args... args)
    {
        action(args...);
    }
};


用于推断参数类型的函数:

// Function template needed because we
// can not deduce the Args... parameter manually in the call.
// so we let the compiler deduce it for us.
template<int size, int index, typename ArgumentTupple, typename Action, typename ...Args>
void doCall(Action action, std::unique_ptr<ResultSetRow>& row, Args... args)
{
    Caller<size, index, ArgumentTupple, Action, Args...>::call(action, row, args...);
}


#1 楼

我发现您的实现过程比必要的复杂。您想要做的是


通过以特定顺序调用其row从“结果集” getValue()中获取参数;
使用它们(作为参数)来调用operator()函数对象action

无需递归即可完成两行:

Do{row->getValue(std::get<N>(args))...};
action(std::get<N>(args)...);


其中args是一个元组。
< br _Range


好吧,现在让我们退一步看看这是如何实现的。首先,我们学习如何从0计数到给定的数量L,以构造范围0, ..., L-1

// holds any number of size_t parameters
template <size_t... N>
struct sizes { using type = sizes <N...>; };

// given L>=0, generate sequence <0, ..., L-1>
template <size_t L, size_t I = 0, typename S = sizes <> >
struct Range;

template <size_t L, size_t I, size_t... N>
struct Range <L, I, sizes <N...> > : Range <L, I+1, sizes <N..., I> > { };

template <size_t L, size_t... N>
struct Range <L, L, sizes <N...> > : sizes <N...> { };


这是一个非常常见的任务,实际上是从这里借来的。有一个更好的对数(而不是线性)模板深度的实现,但是我想在这里保持简单。

“做”?


接下来,一个非常有用的struct让我们以给定的顺序求值表达式:

// using a list-initializer constructor, evaluate arguments in order of appearance
struct Do { template <typename... T> Do(T&&...) { } };


但是请注意,由于自版本4.7.0起存在错误,GCC以相反的顺序求值, 右到左。一种解决方法是按相反的顺序提供范围L-1, ..., 0,但我在这里没有这样做。

Caller


现在,Caller具有通用的仅两个实际参数ArgumentTupleAction的定义。它还读取元组的大小,例如L,并在第三个参数中构造范围0, ..., L-1:最后,专业化将生成的范围推导为可变的size_t参数N...。类型为ArgumentTuple的本地元组用于存储参数,并且std::get<N>访问其第N个元素。就是这样:

// generic Caller
template<
    typename ArgumentTuple, typename Action,
    typename Indices = typename Range<std::tuple_size<ArgumentTuple>{}>::type
>
struct Caller;


请注意,以上所有代码都可以编译,但是由于我没有数据库基础结构,因此我没有看到它在起作用。我刚刚做了一个简单的定义

// Caller specialization, where indices N... have been deduced
template<typename ArgumentTuple, typename Action, size_t... N>
struct Caller<ArgumentTuple, Action, sizes<N...> >
{
    static void call(Action action, std::unique_ptr<ResultSetRow>& row)
    {
        ArgumentTuple args;
        Do{row->getValue(std::get<N>(args))...};
        action(std::get<N>(args)...);
    }
};


,所以我只希望它对您有用。

很抱歉,如果这看起来像是一次完整的重写而不是复审,但我无能为力:-)至少我保留了部分代码,以便从lambda推论出ArgumentTuple

PS-1如果您的ResultSetRow::getValue()void,则需要将其可变参数调用调整为

struct ResultSetRow { template<typename T> void getValue(T) { } };


,以便每个子表达式的计算结果为int而不是void(您不能拥有由void参数组成的列表初始化器。)

PS-2我怀疑您不是真正在这里管理资源,因此不需要std::unique_ptr;一个普通的ResultSetRow&就足够了。

评论


\ $ \ begingroup \ $
@iavr:您对使用模板的方式有很好的参考吗?我仍然在阅读上面的模板时仍然遇到困难(即使在研究了它们之后,也就是我自己可能无法从头开始编写)。
\ $ \ endgroup \ $
–马丁·约克
2014年3月26日下午5:00

\ $ \ begingroup \ $
@LokiAstari好吧,我有点自学。我所知道的关于模板的最佳资源是Vardevoorde和Josuttis的书,但是即使只有一章(有限的)关于元编程的章节,不幸的是,还没有涵盖C ++ 11的版本。 Stroustrup的第4版“ C ++编程语言”确实涵盖了C ++ 11,并且再次有一章涉及元编程。无论如何,这些都是开始的好地方。专门用于Do,请检查可变参数模板并查找struct pass。那里有一些解释。
\ $ \ endgroup \ $
–iavr
2014年3月26日在9:16



\ $ \ begingroup \ $
@LokiAstari对于Range的“对数深度”版本,请检查此答案并查找make_indexes。
\ $ \ endgroup \ $
–iavr
2014年3月26日9:20



#2 楼

我可能会应用以下更改:


size中将static constexpr用作CallerTraits变量,而不是简单地对static const进行修改。

无论什么函数简单地传递其类型的可变参数推论得出,我将通过通用引用(现在正式称为转发引用)传递args并使用std::forward将结果转发到以下功能:

template<int size, int index, typename ArgumentTupple, typename Action, typename ...Args>
void doCall(Action action, std::unique_ptr<ResultSetRow>& row, Args&&... args)
{
    Caller<size, index, ArgumentTupple, Action, Args...>::call(action, row, std::forward<Args>(args)...);
}


这是一个有点费力而且很长的时间来解释它是如何工作的-您可以在上面的答案中找到很好的解释-但要点是,使用此特定配方可以实现完美的转发:

template<typename X>
void foo(X&& arg)
{
    bar(std::forward<X>(arg));
}


X&&...foo的参数类型与const中相应参数的类型具有相同的bar和参考资格。无论如何,链接比我清楚得多。只需记住该配方,并且要使该配方生效,必须通过函数推导X类型。如果从其他地方知道X,它可能不起作用。

我不是创建带有std::unique_ptr<ResultSetRow>&参数的函数,而是让Caller<...>::calldoCall带有ResultSetRow&并立即取消了对row的引用。我不知道results->getNextRow()的确切返回类型是什么,因此我不会尝试假设它以及主call应该作为参数的类型。


评论


\ $ \ begingroup \ $
@LokiAstari通用参考:isocpp.org/blog/2012/11/…
\ $ \ endgroup \ $
–玉石
2014年3月26日0:04