C++11变长参数对齐

5

这是我想要实现的界面:

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

在运行时,选择字符串中的“?”将与变量参数列表1462、1477匹配。

这是类定义:

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

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

很不幸,这会生成一个错误:

test.cpp:133:12: error: 找不到与“执行”调用的成员函数匹配
select.execute(1462, 1477, [](int ID, std::string const& person, double item1, float item2){
~~~~~^~~~~~~

test.cpp:86:14: note: 候选模板被忽略:无法推断出模板参数 'Action'
void execute(Args... param, Action action)
~~~^~~~~~~

1 个错误已生成。

但如果我稍微改变函数定义(如下),它就可以编译通过了。

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.

我知道这是一些语法糖,其中变量参数列表放置的位置,但我想把变量参数列表放在第一位。有没有什么技巧可以帮助编译器正确推断变量参数列表?


3
我不确定你希望的方式是否可行,但是你可以稍微更改API以便使 execute 返回一个可调用函数,该函数接受您的lambda作为参数。看起来几乎一样: select.execute(1462, 1477)([](int ID...){...}) - lethal-guitar
@lethal-guitar:是的,我已经做过了(但我称之为绑定)。但这会留下用户捕获绑定语句的可能性(这不是我想要的)。auto& bind = select.bind(1462, 1477); - Martin York
3个回答

2

虽然有点不太美观,但您可以使用元组:

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

用法示例:

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

这里有一个替代方案,看起来稍微好看一些但是更冗长:

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

这里有一个短小的is_callable特性,可以使用static_assert来获得更好的错误信息:
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;
};

例如:

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

我的第一次尝试是将参数包放在非推断上下文中:template<class... Args, class Action> void execute_impl(typename identity<Args>::type..., Action);,然后显式地提供 Args...,其中最后一个参数已经被弹出。但由于某种原因,这并不起作用 :( - dyp

1

如果参数包不在函数调用的末尾,它们将无法被推断。

引用自标准文档,14.8.2.1 从函数调用中推导模板参数

关于位于参数列表末尾的参数包的规则:

对于一个出现在参数声明列表末尾的函数参数包,在函数参数包展开后,每个剩余的调用参数的类型 A 都将与函数参数包的声明符 P 的类型进行比较。每次比较都会为后续位置的模板参数包推导模板参数。

其他情况:

对于未出现在参数声明列表末尾的函数参数包,其类型属于不可推导的上下文。


是的。我发现编译器不会让我这样做。我想尝试想出一个替代方案,以便我的类的用户可以直观地使用它(但可能会弄乱类)。 - Martin York
DIP解决方案目前可行,但如果出现问题,它会隐藏最后一个参数的逻辑并混淆编译器错误消息。如果您依赖此类功能,最好编写良好的API文档。 - galop1n

0
最终我决定使用std::tuple作为参数,并隐藏参数扩展:

现在的用法是:

select.execute(
    Bind(1462, 1477), 
    [](int ID, std::string const& person, double item1, float item2){
        std::cout << "Got Row:" 
                  << ID     << ", " 
                  << person << ", " 
                  << item1  << ", " 
                  << item2  << "\n";
});

然后我定义:

template<typename ...Args>
inline std::tuple<Args...> Bind(Args... args) { return std::tuple<Args...>(args...);}

类被重新定义为:

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

        template<class Action, class ...Args>
        void execute(std::tuple<Args...> const& param, Action action);
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接