【问题标题】:use lambda in function template, can't deduce type, makeSet() use case在函数模板中使用 lambda,无法推断类型,makeSet() 用例
【发布时间】:2017-02-20 13:48:37
【问题描述】:

我想要实现的是一个 makeSet() 函数,它接受三个参数、一对迭代器和一个转换值的函数。

一个用例可能是从一系列值创建一个集合并进行转换,例如,将std::map<K,V> 转换为std::set<std::pair<V,K>>.

客户端代码可能看起来像

auto s = makeSet(hash.begin(), hash.end(),
    [](std::pair<int,int> x) { return std::make_pair(x.second, x.first); });

我目前的尝试如下,

// (commented code are some other *failed* attempt).
template <typename Iterator,
        typename T = typename std::iterator_traits<Iterator>::value_type,
        template<typename ... > class Monad, typename R >
        // typename R, typename Monad = std::function<R(T)> >
std::set<R> makeSet(Iterator first, Iterator last, Monad<R,T> f) {
    std::set<R> res;
    for (; first != last; ++first) res.insert(f(*first));
    return res;
}

但不幸的是不起作用。这个问题看起来好像没有推导出 R。

有什么解决方案或解决方法吗? 如果您能告诉我正确的方法,我将非常感激。

【问题讨论】:

    标签: c++ c++11 templates lambda type-deduction


    【解决方案1】:

    lambda 表达式的类型是一个未命名的类类型(它的闭包类型),而不是std::function。因此,您不能从中推断出std::functionMonad

    您最好的选择是做标准库所做的事情,并简单地接受任何东西作为谓词:

    template <
      class Iterator,
      class UnaryFunction
    >
    auto makeSet(Iterator first, Iterator last, UnaryFunction f) -> std::set<decltype(f(*first))>
    {
      std::set<decltype(f(*first))> res;
      for (; first != last; ++first) res.insert(f(*first));
      return res;
    }
    

    请注意,您可能必须将decltype 包裹在std::remove_reference 和/或std::remove_cv 中以涵盖所有极端情况(或者,如@Yakk 所建议的那样,std::decay)。

    另外,为避免重新发明轮子,您可能需要查看 Boost.Range 库。

    【讨论】:

    • 哦,心爱的,有时挑剔的decltype!
    • 范围是指使用范围对象而不是一对迭代器,类似于this,还是不同的东西?
    • 我只会使用std::decay_t&lt;decltype(blah)&gt;,因为decay_t 使类型适合存储。第二点是你几乎不应该推断std::function的类型; std::function 是类型擦除类,类型推导和类型擦除是对立的。推断要擦除的类型是设计缺陷的标志。你擦除是因为你需要一个固定的类型;你推断是因为你知道这里的确切类型。如果您知道确切的类型,999/1000 次您不需要固定类型。
    【解决方案2】:

    “你对管道的考虑越多,堵塞排水管就越容易。” -- Scotty,星际迷航 III。

    没有必要像那样过度设计模板功能。只需使用转发引用,让您的 C++17 编译器解决所有问题。

    #include <set>
    #include <map>
    #include <utility>
    #include <type_traits>
    
    // (commented code are some other *failed* attempt).
    template <typename Iterator, typename Lambda>
    auto makeSet(Iterator first, Iterator last, Lambda &&f) {
    
        typedef typename std::remove_reference<decltype(first->first)>::type const_first_t;
    
        typedef typename std::remove_const<const_first_t>::type first_t;
    
        typedef typename std::remove_reference<decltype(first->second)>::type second_t;
    
        typedef std::pair<first_t, second_t> R;
    
    
        std::set<R> res;
    
        for (; first != last; ++first) res.insert(f(*first));
        return res;
    }
    
    
    void foo()
    {
        std::map<int, int> m;
    
        std::set<std::pair<int, int>> s =
            makeSet(m.begin(), m.end(),
                [](const auto &x)
                {
                    return std::make_pair(x.second, x.first);
                });
    
    }
    

    【讨论】:

    • R 应该是 f(*first) 的衰减类型,而不是你在这里写的。
    • 好点!虽然我更喜欢更简洁的版本。这就是为什么我无法提出类似的解决方案,寻找外行太令人生畏。
    • 正如@Barry 指出的那样,尽管想法相似,但需要更通用的 R。
    • 另外,这个解决方案能否适应 C++03,——不使用decltype——没有太多麻烦?
    • 我在 C++03 中没有看到任何版本的简单路径。除非您在附加模板参数中明确指定所有内容的类型,否则不会。底线:升级你的编译器:-)
    猜你喜欢
    • 2021-09-11
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-05
    • 1970-01-01
    • 2021-03-04
    相关资源
    最近更新 更多