简体   繁体   English

如何管理需要从递归仿函数/lambda 派生的模板参数的声明?

[英]How do I manage declarations that require template parameters derived from recursive functors/lambdas?

I am attempting to build a clean and neat implementation of recursive-capable lambda self-scoping (which is basically a Y-combinator although I think technically not quite).我正在尝试构建具有递归功能的 lambda 自范围的干净整洁的实现(虽然我认为技术上不完全是一个 Y 组合器)。 It's a journey that's taken me to, among many others, this thread and this thread and this thread .这是一个带我去的旅程,除其他外,这个线程这个线程这个线程

I've boiled down one of my issues as cleanly as I can: how do I pass around templated functors which take lambdas as their template parameters?我已经尽可能清楚地总结了我的一个问题:如何传递以 lambda 作为其模板参数的模板仿函数?

#include <string>
#include <iostream>
#define uint unsigned int

template <class F>
class Functor {
public:
    F m_f;

    template <class... Args>
    decltype(auto) operator()(Args&&... args) {
        return m_f(*this, std::forward<Args>(args)...);
    }
};
template <class F> Functor(F)->Functor<F>;

class B {
private:
    uint m_val;
public:
    B(uint val) : m_val(val) {}
    uint evaluate(Functor<decltype([](auto & self, uint val)->uint {})> func) const {
        return func(m_val);
    }
};

int main() {
    B b = B(5u);
    Functor f = Functor{[](auto& self, uint val) -> uint {
        return ((2u * val) + 1u);
    }};

    std::cout << "f applied to b is " << b.evaluate(f) << "." << std::endl;
}

The code above does not work, with Visual Studio claiming that f (in the b.evaluate(f) call) does not match the parameter type.上面的代码不起作用,Visual Studio 声称f (在b.evaluate(f)调用中)与参数类型不匹配。

My assumption is that auto & self is not clever enough to make this work.我的假设是auto & self不够聪明,无法完成这项工作。 How do I get around this?我该如何解决这个问题? How do I store and pass these things around when they are essentially undefinable?当它们基本上无法定义时,我如何存储和传递这些东西? Is this why many of the Y-combinator implementations I've seen have the strange double-wrapped thing?这就是为什么我见过的许多 Y-combinator 实现都有奇怪的双重包装的原因吗?

Any help or explanation would be enormously appreciated.任何帮助或解释将不胜感激。

The only way I see is make evaluate() a template method;我看到的唯一方法是让evaluate()成为模板方法; if you want to be sure to receive a Functor (but you can simply accept a callable: see Yakk's answer ):如果你想确保收到一个Functor (但你可以简单地接受一个可调用的:见Yakk 的回答):

template <typename F>
uint evaluate(Functor<F> func) const {
    return func(m_val);
}

Take in count that every lambda is a different type, as you can verify with the following trivial code考虑到每个 lambda 都是不同的类型,您可以使用以下简单代码进行验证

auto l1 = []{};
auto l2 = []{};

static_assert( not std::is_same_v<decltype(l1), decltype(l2)> );

so impose a particular lambda type to evaluate() can't work because if you call the method with (apparently) the same lambda function, the call doesn't match, as you can see in the following example所以强加一个特定的 lambda 类型来evaluate()不能工作,因为如果你用(显然)相同的 lambda function 调用方法,你可以看到下面的调用不匹配,

auto l1 = []{};
auto l2 = []{};

void foo (decltype(l1))
 { }

int main ()
 {
   foo(l2); // compilation error: no matching function for call to 'foo'
 }

The easiest solution is:最简单的解决方案是:

uint evaluate(std::function<uint(uint)> func) const {
    return func(m_val);
}

a step up would be to write a function_view .一个进步将是编写一个function_view

uint evaluate(function_view<uint(uint)> func) const {
    return func(m_val);
}

(there are dozens of implementations on the net, should be easy to find). (网上有几十个实现,应该很容易找到)。

The easiest and most runtime efficient is:最简单和最高效的运行时间是:

template<class F>
uint evaluate(F&& func) const {
    return func(m_val);
}

because we don't care what func is, we just want it to quack like a duck.因为我们不关心func是什么,我们只希望它像鸭子一样嘎嘎叫。 If you want to check it early...如果你想早点检查...

template<class F> requires (std::is_convertible_v< std::invoke_result_t< F&, uint >, uint >)
uint evaluate(F&& func) const {
    return func(m_val);
}

using , or using 使用 ,或使用

template<class F,
  std::enable_if_t<(std::is_convertible_v< std::invoke_result_t< F&, uint >, uint >), bool> = true
>
uint evaluate(F&& func) const {
    return func(m_val);
}

which is similar just more obscure.这是相似的只是更模糊。

You can write a fixes-signature type-erased Functor , but I think it is a bad idea.您可以编写一个 fix-signature type- Functor ,但我认为这是一个坏主意。 It looks like:看起来像:

template<class R, class...Args>
using FixedSignatureFunctor = Functor< std::function<R( std::function<R(Args...)>, Args...) > >;

or slightly more efficient或稍有效率

template<class R, class...Args>
using FixedSignatureFunctor = Functor< function_view<R( std::function<R(Args...)>, Args...) > >;

but this is pretty insane;但这太疯狂了; you'd want to forget what the F is, but not that you can replace the F !你想忘记F是什么,但不是你可以替换F

To make this fully "useful", you'd have to add smart copy/move/assign operations to Functor , where it can be copied if the F s inside each of them can be copied.为了使其完全“有用”,您必须向Functor添加智能复制/移动/分配操作,如果可以复制其中的每个F ,则可以复制它们。

template <class F>
class Functor {
public:
  // ...
  Functor(Functor&&)=default;
  Functor& operator=(Functor&&)=default;
  Functor(Functor const&)=default;
  Functor& operator=(Functor const&)=default;

  template<class O> requires (std::is_constructible_v<F, O&&>)
  Functor(Functor<O>&& o):m_f(std::move(o.m_f)){}
  template<class O> requires (std::is_constructible_v<F, O const&>)
  Functor(Functor<O> const& o):m_f(o.m_f){}
  template<class O> requires (std::is_assignable_v<F, O&&>)
  Functor& operator=(Functor<O>&& o){
    m_f = std::move(o.mf);
    return *this;
  }
  template<class O> requires (std::is_assignable_v<F, O const&>)
  Functor& operator=(Functor<O> const& o){
    m_f = o.mf;
    return *this;
  }
  // ...
};

( version, replace requires clauses with std::enable_if_t SFINAE hack in and before). 版本,在及之前的版本中,用std::enable_if_t SFINAE hack 替换 requires 子句)。

How to decide如何决定

The core thing to remember here is that C++ has more than one kind of polymorphism, and using the wrong kind will make you waste a lot of time.这里要记住的核心是 C++ 具有不止一种多态性,使用错误的种类会让你浪费很多时间。

There is both compile time polymorphism and runtime polymorphism.有编译时多态性和运行时多态性。 Using runtime polymorphism when you only need compile time polymorphism is a waste.当您只需要编译时多态时使用运行时多态是一种浪费。

Then in each category, there are even more subtypes.然后在每个类别中,还有更多的子类型。

std::function is a runtime polymorphic type erasure regular object. std::function是运行时多态类型擦除正则 object。 Inheritance based virtual functions is another runtime polymorphic technique.基于 Inheritance 的虚函数是另一种运行时多态技术。

Your Y-combinator is doing compile time polymorphism.您的 Y 组合器正在执行编译时多态性。 It changes what it stores and exposed a more uniform interface.它改变了它存储的内容并暴露了一个更统一的界面。

Things talking to that interface don't care about the internal implementation details of your Y-combinator, and including them in their implementation is an abstraction failure.与该接口对话的事物并不关心Y-combinator 的内部实现细节,将它们包含在它们的实现中是一个抽象失败。

evaluate takes a callable thing and pass it in uint and expects a uint in return. evaluate接受一个可调用的东西并将它传递给uint并期望一个uint作为回报。 That is what it care about.这就是它所关心的。 It doesn't care if it is passed a Functor<Chicken> or a function pointer.它不关心是否传递了Functor<Chicken>或 function 指针。

Making it care about it is a mistake.让它关心它是一个错误。

If it takes a std::function , it does runtime polymorphism;如果它需要一个std::function ,它会执行运行时多态性; if it takes a template<class F> with an argument of type F&& , it is compile time polymorphic.如果它采用带有F&&类型参数的template<class F> ,则它是编译时多态的。 This is a choice, and they are different.这是一个选择,它们是不同的。

Taking a Functor<F> of any kind is putting contract requirements in its API it fundamentally shouldn't care about.采用任何类型的Functor<F>都会将合同要求放在它根本不应该关心的 API 中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何从派生类访问模板参数? - How to access template parameters from a derived class? 如何从函子的元组中返回返回值的元组? - How do I return a tuple of return values from a tuple of functors? C ++:如何要求一个模板类型派生自另一个模板类型 - C++: How to require that one template type is derived from the other 通过派生类模板参数指定其中一个基类模板参数。 如何使用基类使用声明 - Specifying one of base class template parameters by derived class template parameter. How to use base class's using declarations 如何将模板参数派生的编译时信息添加到模板中? - How to add compile time information derived from template parameters to template? 如何在一对中存储(伴随)函子? - How do I store (adjoint) functors in a pair? 如何使用仿函数对循环进行矢量化? - How do I vectorize loop with functors? 如何避免从另一个模板类(C ++)派生的模板类中的非依赖名称使用多个“使用”声明? - How to avoid multiple “using” declarations at non-dependent names in a template class derived from another template class (c++)? 如何实例化模板仿函数 F<d> 在多个仿函数 F1、F2 和多个模板参数 D1、D2 上? </d> - How to instantiate templated functors F<D> over multiple functors F1,F2 and multiple template parameters D1,D2? 函数/仿函数作为模板参数。 他们可以存储吗? - Functions/functors as template parameters. Can they be stored?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM