英文:
Deducing return type of a capturing lambda function `operator()` without an instantiated lambda variable
问题
我试图做的是从执行lambda的类内部推断捕获lambda的返回类型,以便返回类型不需要是类签名的一部分。
由于lambda正在捕获,默认构造函数被删除,所以我写了这样的代码:
using AppliedReturnType =
decltype
(
std::apply
(
[]( auto& ...items )
{
std::optional< ApplyLambda > lambda; // 这里使用optional来绕过构造
return lambda.value()( items... ); // 在未初始化的情况下使用value
},
t_
)
);
我是否正确地假设,由于我仅在decltype评估中使用此代码,所以这样的代码无论运行时的std::optional实现/UB如何都不是问题?
并且出于好奇,是否有更好的方法来做类似的事情?(不在类模板列表中具有类型是必需的)
编辑:根据答案简化了实现,以便不会对从这样一个问题复制粘贴代码的其他读者的眼睛造成太大影响...
下面是一个简化的实现(非常简化,因为我确信没有人会费心处理300多行的代码...):
#include <iostream>
#include <optional>
#include <tuple>
template< typename Callable, typename Tuple >
class C
{
public:
C( Callable&& callable, Tuple&& t )
: t_{ std::move( t ) }
, result_
{
std::apply
(
callable,
t_
)
}
{}
auto result() const { return result_; }
private:
Tuple t_;
using AppliedReturnType =
decltype
(
std::apply
(
[]( auto& ...items )
{
std::optional< Callable > callable;
return callable.value()( items... );
},
t_
)
);
AppliedReturnType result_;
};
int main()
{
int i = 10;
auto lambda = [&i]( auto&... items ) { return ( items + ...) + i; };
C c{ std::move( lambda ), std::tuple{ 1, 2, 3 } };
std::cout << c.result() << '\n';
}
英文:
What I'm trying to do is to deduce return type of a capturing lambda from inside the class where the lambda is executed (never stored) so that the return type is not required to be part of class signature.
Since lambda is capturing the default constructor is deleted so I wrote such code:
using AppliedReturnType =
decltype
(
std::apply
(
[]( auto& ...items )
{
std::optional< ApplyLambda > lambda; // optional used here to work around construction
return lambda.value()( items... ); // value used without initialization
},
t_
)
);
Am I correct in assumption that since I'm using this code only in decltype evaluation I'd say that such code is not a problem no matter the std::optional implementation/UB at runtime?
And out of curiosity, is there a better way to do something like this? (not having the type in class template list is a must)
EDIT: editing simplified implementation a bit (based on the answers) so that it won't hurt so much the eyes of other readers that would copy paste code from such a question...
Below is a simplified implementation (extremely dumbed down as I'm certain that nobody would bother with 300+ lines of code...):
#include <iostream>
#include <optional>
#include <tuple>
template< typename Callable, typename Tuple >
class C
{
public:
C( Callable&& callable, Tuple&& t )
: t_{ std::move( t ) }
, result_
{
std::apply
(
callable,
t_
)
}
{}
auto result() const { return result_; }
private:
Tuple t_;
using AppliedReturnType =
decltype
(
std::apply
(
[]( auto& ...items )
{
std::optional< Callable > callable;
return callable.value()( items... );
},
t_
)
);
AppliedReturnType result_;
};
int main()
{
int i = 10;
auto lambda = [&i]( auto&... items ) { return ( items + ...) + i; };
C c{ std::move( lambda ), std::tuple{ 1, 2, 3 } };
std::cout << c.result() << '\n';
}
答案1
得分: 4
你说得对,由于一切都在`decltype`中,没有问题,尽管你可以用以下方式简化:
```cpp
using AppliedReturnType = decltype(std::apply(std::declval<ApplyLambda>(), t_));
正如评论中指出的那样,在你的构造函数中也不需要那个内部lambda:
C(ApplyLambda& lambda, Tuple&& t)
: t_{std::move(t)}
, result_{std::apply(lambda, t_)}
{}
<details>
<summary>英文:</summary>
You are correct that since everything is in `decltype`, there is no issue, although you can simplify that stuff with
using AppliedReturnType = decltype(std::apply(std::declval<ApplyLambda>(), t_));
---
And as pointed out in the comments, you do not need that inner lambda in your constructor either
C( ApplyLambda& lambda, Tuple&& t )
: t_{ std::move( t ) }
, result_{ std::apply( lambda, t_ ) }
{}
[Demo](https://godbolt.org/z/4qsGqY1oW)
</details>
# 答案2
**得分**: 2
```cpp
模板参数不要命名为“Lambda”——没有理由要求一个lambda,任何可调用的东西都可以。所以只需“F”、“ApplyF”或“Callable”之类的。
变量的名称也是同样的情况。
这:
```cpp
[&lambda]( auto& ...items )
{
return lambda( items... );
},
这是一个非常冗长且不正确的写法,可以简写为“lambda”。在这里它能够工作,因为你将元组作为左值传递,但如果你转发它,那么元组的元素将被移动,而“auto&...”将不会编译。
同样,如果可调用的返回类型是引用类型(例如“int&”),你会丢弃它,这种写法返回的是“int”。
只需“lambda”。
同样适用于这个:
using AppliedReturnType =
decltype
(
std::apply
(
[]( auto& ...items )
{
std::optional< ApplyLambda > lambda;
return lambda.value()( items... );
},
t_
)
);
与上面一样,这里传入的lambda可以直接是“lambda”,但你还没有该类型的对象——所以可以使用“declval”创建一个:
using AppliedReturnType = decltype(std::apply(std::declval<F&>(), std::declval<Tuple&>());
注意“&”,因为你将其作为左值传递。
在这里:
C( ApplyLambda& lambda, Tuple&& t )
不要通过左值引用接受可调用对象——因为这(具有讽刺意味的是)禁止传递lambda。
将所有内容放在一起:
template <typename F, typename Tuple>
class C
{
using R = decltype(std::apply(std::declval<F&>(), std::declval<Tuple&>()));
public:
Tuple t_;
R result_;
public:
C( F f, Tuple&& t )
: t_{ std::move( t ) }
, result_{std::apply(f, t_)}
{}
R result() const { return result_; }
};
最后说明一下,我不确定为什么“C”会有用,考虑到存在“std::apply”。
<details>
<summary>英文:</summary>
I have a bunch of comments here:
---
```cpp
template< typename ApplyLambda, typename Tuple >
Don't name template parameters Lambda
- there's no reason to ever require a lambda, any callable is fine. So just F
, or ApplyF
, or Callable
, or something.
The same thing applies to the name of the variable.
This:
[&lambda]( auto& ...items )
{
return lambda( items... );
},
This is a very long, and incorrect, way of writing just lambda
. It works here because you're passing the tuple as an lvalue, but if you forwarded it, then the elements of the tuple would be moved, and auto&...
wouldn't compile.
Also if the callable returned a reference type (e.g. int&
), you're dropping that on the floor, this spelling returns int
.
Just: lambda
.
Likewise this:
using AppliedReturnType =
decltype
(
std::apply
(
[]( auto& ...items )
{
std::optional< ApplyLambda > lambda;
return lambda.value()( items... );
},
t_
)
);
as above, the lambda passed in here could just be lambda
, but you don't have an object of that type yet - so you can make one using declval
:
using AppliedReturnType = decltype(std::apply(std::declval<F&>(), std::declval<Tuple&>());
Note the &
because you're passing it as an lvalue.
Here:
C( ApplyLambda& lambda, Tuple&& t )
Don't take callables by lvalue references - since it (ironically) prohibits passing in lambdas.
Putting it all together:
template <typename F, typename Tuple>
class C
{
using R = decltype(std::apply(std::declval<F&>(), std::declval<Tuple&>()));
public:
Tuple t_;
R result_;
public:
C( F f, Tuple&& t )
: t_{ std::move( t ) }
, result_{std::apply(f, t_)}
{}
R result() const { return result_; }
};
As a final note, I'm not sure why C
would be useful, given that std::apply
exists.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论