代码之家  ›  专栏  ›  技术社区  ›  grunt-lucas

在C++中创建U到V映射器函数模板的惯用方法是什么?

  •  3
  • grunt-lucas  · 技术社区  · 1 年前

    我现在正在学习C++,并尝试一些模板功能。我正在尝试创建一个接收函数的通用模板 F 从…起 U V std::array 的类型 U ,然后(通过NRVO)返回类型为的数组 五、 .

    这是我第一次通过时想到的。在我未经训练的眼中,这似乎是合理的,但编译器不喜欢:

    template <typename F, typename U, typename V, std::size_t N> std::array<V, N> map(F mapper, const std::array<U, N> &elements)
    {
      std::array<V, N> newArray{};
      for (std::size_t i = 0; i < N; i++) {
        newArray[i] = mapper(elements[i]);
      }
      return newArray;
    }
    
    // ...
    
    int main()
    {
      std::array<int, 10> ints1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
      auto doubles1 = map([](int x) -> double { return x*2.5; }, ints1);
    }
    

    我正在使用clang 16.0.6,它告诉我:

    functional.cpp:87:19: error: no matching function for call to 'map'
      auto doubles1 = map([](int x) -> double { return x*2.5; }, ints1);
                      ^~~~~~~~~~~~
    functional.cpp:22:79: note: candidate template ignored: couldn't infer template argument 'V'
    template <typename F, typename U, typename V, std::size_t N> std::array<V, N> map(F mapper, std::array<U, N> &elements)
                                                                                  ^
    1 error generated.
    

    在我看来,我似乎可以做一些事情来提示编译器lambda返回一个双精度,因此 auto 应推断为 std::array<double, N> 。有什么容易的东西我遗漏了吗?非常感谢。

    我当然希望代码能够编译,但有些地方不太对劲。我对C++语言还很陌生,所以我很难知道该用谷歌搜索什么来解决这个问题。我提出的一个可能的解决方案是使用out参数:

    template <typename F, typename U, typename V, std::size_t N> void map_outParam(F mapper, std::array<U, N> &elements, std::array<V, N> &out)
    {
      for (std::size_t i = 0; i < N; i++) {
        out[i] = mapper(elements[i]);
      }
    }
    

    这编译得很好,并且如预期的那样工作。然而,它并没有我想要的那么干净。

    3 回复  |  直到 1 年前
        1
  •  3
  •   Christian Stieber    1 年前

    基本上,您可以使用回调函数的结果类型来计算结果数组的类型。

    #include <type_traits>
    #include <array>
    
    template <typename F, typename U, std::size_t N> auto map(F mapper, std::array<U, N> &elements)
    {
      std::array<std::invoke_result_t<F, U>, N> newArray{};
      for (std::size_t i = 0; i < N; i++) {
        newArray[i] = mapper(elements[i]);
      }
      return newArray;
    }
    
    // ...
    
    int main()
    {
      std::array<int, 10> ints1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
      auto doubles1 = map([](int x) -> double { return x*2.5; }, ints1);
    }
    
        2
  •  3
  •   goober    1 年前

    您很接近,但模板参数的顺序令人困惑:

    #include <cstddef>
    #include <array>
    #include <iostream>
    
    template <typename V, std::size_t N, typename F, typename U>
    std::array<V, N> map(
        F mapper,
        std::array<U, N>& elements
    ) {
      std::array<V, N> newArray{};
      for (std::size_t i = 0; i < N; i++) {
        newArray[i] = mapper(elements[i]);
      }
      return newArray;
    }
    
    // ...
    
    int main()
    {
        std::array<int, 10> ints1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        auto doubles1 = map<double>(
            [](int x) -> double {
                return x*2.5; 
            },
            ints1
        );
    
        for (auto it = doubles1.begin(); it != doubles1.end(); ++it) {
            std::cout<<*it<<'\n';
        }
    }
    

    不同之处在于有一个隐式模板参数的概念,编译器在其中推导模板类型。你最初订购的方式不尊重这一点 V 无法推导,因此没有匹配函数。

    因此,上面的示例调整了排序,并明确定义了 五、

        3
  •  1
  •   康桓瑋    1 年前

    映射的元素可能不是默认可构造的。

    在的帮助下,您可以更轻松地完成此操作 std::apply std::array 的CTAD

    template <typename F, typename U, std::size_t N> 
    auto map(F mapper, std::array<U, N>& elements)
    {
      return std::apply([&](auto&... args) {
        return std::array{mapper(args)...};
      }, elements);
    }