将可变参数模板参数与lambda相结合

时间:2016-11-20 07:21:54

标签: c++ gcc c++14 variadic-templates variadic-functions

我知道现代C ++中的可变参数模板是什么,但我无法绕过它以便能够编写如下代码:

#include <iostream>
#include <sstream>
using namespace std;


template <typename... Args, typename Combinator>
auto combine(Args... args, Combinator combinator)
{
    auto current_value = combinator(args...);
    return current_value;
}

int main() {
    auto comb = combine(1, "asdf"s, 14.2,
                [](const auto& a, const auto& b, const auto& c) { 
                    stringstream ss;
                    ss << a << "\n";
                    ss << b << "\n";
                    ss << c << "\n";
                    return ss.str();
                });

    return 0;
}

换句话说,我想向函数提供不同类型的不同类型的参数,但最后一个参数是lambda或用于以某种方式组合参数的任何可调用对象。这个例子看起来纯粹是学术性的,但在这个例子的基础上,我想构建更多时髦的代码,但首先我需要这个来编译。我希望你能帮忙!

我无法编译。我不知道我错过了什么。

以下是GCC吐痰的内容:

In function 'int main()':
21:6: error: no matching function for call to 'combine(int, std::basic_string<char>, double, main()::<lambda(auto:1&, auto:2&, auto:3&)>)'
21:6: note: candidate is:
7:6: note: template<class ... Args, class Combinator> auto combine(Args ..., Combinator&&)
7:6: note: template argument deduction/substitution failed:
21:6: note: candidate expects 1 argument, 4 provided

3 个答案:

答案 0 :(得分:8)

可变参数模板必须是最后一个参数,因此可以推导出来,参见Template argument deduction

  

非推断的上下文

     

7)参数P是一个参数包,不会出现在参数列表的末尾:

template<class... Ts, class T> void f1(T n, Ts... args);
template<class... Ts, class T> void f2(Ts... args, T n);
f1(1, 2, 3, 4); // P1 = T, A1 = 1: deduced T = int
                // P2 = Ts..., A2 = 2, A3 = 3, A4 = 4: deduced Ts = [int, int, int]
f2(1, 2, 3, 4); // P1 = Ts...: Ts is non-deduced context

您应将其更改为:

#include <iostream>
#include <sstream>
using namespace std;


template <typename... Args, typename Combinator>
auto combine(Combinator combinator, Args&&... args)
{
    auto current_value = combinator(std::forward<Args>(args)...);
    return current_value;
}

int main() {
    auto comb = combine([](const auto& a, const auto& b, const auto& c) { 
                    stringstream ss;
                    ss << a << "\n";
                    ss << b << "\n";
                    ss << c << "\n";
                    return ss.str();
                },
                1, "asdf"s, 14.2);
    std::cout << comb;
    return 0;
}

答案 1 :(得分:3)

  

换句话说,我想给函数提供一个未知数量的不同类型的参数,但是最后一个参数是lambda或用于以某种方式组合参数的任何可调用对象。

由于传递callable作为 last 参数似乎是你问题的关键,这是一种方法:

namespace detail {
    template<typename TupT, std::size_t... Is>
    auto combine(TupT&& tup, std::index_sequence<Is...>) {
        return std::get<sizeof...(Is)>(tup)(std::get<Is>(std::forward<TupT>(tup))...);
//             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  last element in tuple is the callable
    }
}

template<typename... Ts>
auto combine(Ts&&... ts) {
    return detail::combine(
        std::forward_as_tuple(std::forward<Ts>(ts)...),
        std::make_index_sequence<sizeof...(Ts) - 1>{}
//                               ^^^^^^^^^^^^^^^^^  arg count = size of pack - 1 (callable)
    );
}

Online Demo

这也实现了完美转发,这在您的问题实施中没有找到。

答案 2 :(得分:2)

您的代码确实无法编译,因为只有当变量模板参数是最后一个参数时才能推断它们。

要做到这一点而不改变你的界面,你可以这样做:

#include <iostream>
#include <sstream>

using namespace std;

template <typename... Args, typename Combinator>
auto combine_impl(Args... args, Combinator combinator) {
    return combinator(args...);
}

template <typename... Args>
auto combine(Args... args) {
    return combinator_impl<Args...>(args...);
}

int main() {
    auto comb = combine(1, "asdf"s, 14.2,
        [](const auto& a, const auto& b, const auto& c) {
            stringstream ss; ss << a << "\n"; ss << b << "\n"; ss << c << "\n";
            return ss.str();
        }
    );

    return 0;
}

但坦率地说,如果只是这样做:

auto comb = [](const auto& a, const auto& b, const auto& c) {
    stringstream ss; ss << a << "\n"; ss << b << "\n"; ss << c << "\n";
    return ss.str();
}(1, "asdf"s, 14.2);

如果你不能在同一行上调用并声明一个lambda,你可以使用C ++ 17 std::invoke

auto comb = std::invoke([](const auto& a, const auto& b, const auto& c) {
    stringstream ss; ss << a << "\n"; ss << b << "\n"; ss << c << "\n";
    return ss.str();
}, 1, "asdf"s, 14.2);

请注意,最后两个版本比第一个版本更快,因为它们保留了值类型。