【问题标题】:Combine variadic template arguments with a lambda将可变参数模板参数与 lambda 组合
【发布时间】:2016-11-20 07:21:53
【问题描述】:

我知道现代 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

【问题讨论】:

  • 为什么 lambda/callable 必须是最后一个参数而不是第一个参数,makes this trivial?
  • 首先我确实这样做了。这是微不足道的,但我现在正在处理的使用我提出的构造的代码并不意味着自动生成。它旨在由用户编写。将组合子指定为最后一个参数,因为它更美观且语义更正确。这很容易说:combine this and that with this combinator 而不是 combine with this combinator this and that

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


【解决方案1】:

可变参数模板必须是最后一个参数,这样才能推导出来,见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;
}

【讨论】:

    【解决方案2】:

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

    由于将可调用对象作为 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

    这也实现了完美转发,这是您问题的实现中缺少的。

    【讨论】:

      【解决方案3】:

      您的代码确实无法编译,因为可变参数模板参数只有在它们是最后一个参数时才能被推导出来。

      要在不改变界面的情况下做到这一点,你可以这样做:

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

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

      【讨论】:

        猜你喜欢
        • 2012-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-08
        • 2015-07-07
        • 2011-11-24
        • 2016-12-01
        相关资源
        最近更新 更多