如何为通用Lambda参数定义模板参数?

时间:2019-04-21 15:33:02

标签: c++ templates lambda c++17 template-deduction

说明:

CLion及其标准编译器给我一个错误,当我为以lambda作为参数的泛型函数编写lambda作为参数时,“忽略了[候选模板]”。 此lambda采用通用类型T,并返回另一个未知类型A

我正在编写的容器类应该支持诸如Scala中的功能操作或Java Stream API中的功能操作。

确切地说: 地图功能带来了巨大的问题。它在名为Sequence的类中作为成员函数实现,该类采用通用参数T。 应该采用一个已知类型T的元素(实际上会遍历整个序列)并将其转换为未知类型A。 实现本身不是问题,但是我无法使用我知道的lambda语法调用该函数。

代码:

Sequence.h

template< typename T >
class Sequence {
public:
    template< typename A >
    auto map( std::function< A( const T ) > function ) const {
        auto sequence = new Sequence< A >;
        for ( const T& element : *this ) {
            sequence->push( function( element ) );
        }
        return *sequence;
    }
}

main.cpp

int main() {
    Sequence< uint32_t > a;
    a.push( 20 );
    a.push( 30 );
    a.push( 40 );

    a.map( []( uint32_t c ) -> uint32_t {
        return c * c;
    } );
    return 0;
}

据我了解,lambda已初始化, 接受类型为std::uint32_t的参数,并返回类型为std::uint32_t的值。 到目前为止,通用参数A似乎还没有得出。

错误堆栈:

main.cpp:21:7: error: no matching function for call to 'Sequence<unsigned int>::map(main()::<lambda(uint32_t)>)'
     } );

Sequence.h:143:10: note: candidate: template<class A> auto Sequence<T>::map(std::function<A(T)>) const [with A = A; T = unsigned int]
     auto map( std::function< A( const T ) > function ) const {

note:   template argument deduction/substitution failed:
main.cpp:21:7: note:   'main()::<lambda(uint32_t)>' is not derived from 'std::function<A(unsigned int)>'
     } );

谢谢!

2 个答案:

答案 0 :(得分:2)

忽略const问题,您遇到了种鸡与蛋的问题。

确实可以将您的lambda转换为std::function<std::uint32_t(std::unit32_t)>

但是lambda不是std::function<std::uint32_t(std::unit32_t)>也是正确的,因此编译器无法推断A

如果编译器无法推论A,就不能将lambda转换为std::function<A(T)>

您显然可以显式地调用std::function的正确map()

a.map(std::function<std::uint32_t(std::uint32_t)>{[]( uint32_t c ) -> uint32_t {
    return c * c;
}});

,并算出您正在使用C ++ 17(因此您可以使用std::function的推导指南)还推导std::function的模板参数

a.map(std::function{[]( uint32_t c ) -> uint32_t {
    return c * c;
}});

但是,再次使用std::function的模板推导指南,编写mat()接受一个简单的可调用对象并从中推导A怎么办?

我的意思是……怎么办?

template <typename F>
auto map( F && func ) const {
    using A = typename decltype(std::function{std::forward<F>(func)})::result_type;

    auto sequence = new Sequence< A >;
    for ( const T& element : *this ) {
        sequence->push( std::forward<F>(func)( element ) );
    }
    return *sequence;
}

(警告:代码未经测试)。

如迈克尔·肯泽尔(Michael Kenzel)的建议,您还可以推导A,而无需std::function推导指南(在C ++ 17之前)。

答案 1 :(得分:1)

正如@cpplearner在上面的评论中已经指出的那样,在Constructing std::function argument from lambda中对此进行了详细说明。

如果Sequence已经是模板,则没有理由要求将map函数的可调用对象以std::function的形式传递。至少,我似乎无法提出可以证明这样做的理由。 std::function通常不能随意构造或调用,并且也可以禁止内联。除非您确实需要存储任意可调用对象以供以后使用的能力,否则最好避免使用它。只需获取转发参考,例如:

template <typename F>
auto map(F&& f) const;

您应该能够推断出在序列元素上调用f的任何结果类型,例如通过

decltype(f(std::declval<T>()))

此外,不要仅仅返回指向新Sequence的原始指针。使用std::unique_ptr

将所有内容放在一起,您的map函数可能看起来像这样:

template <typename F>
auto map(F&& f) const
{
    using output_element_type = decltype(f(std::declval<T>()));

    auto sequence = std::make_unique<Sequence<output_element_type>>();

    for (const T& element : *this)
        sequence->push(f(element));

    return sequence;
}