Lambda 作为默认参数失败
Lambda as default argument fails
我在使用最新版本的 clang 和 gcc 时遇到错误,代码如下:
int main() {
auto lambda = [] (auto = [] {}) {};
lambda();
}
Clang 报错:
prog.cc: In function 'int main()':
prog.cc:3:12: error: no match for call to '(main()::<lambda(auto:1)>) ()'
lambda();
^
prog.cc:2:35: note: candidate: template<class auto:1> main()::<lambda(auto:1)>
auto lambda = [] (auto = [] {}) {};
^
prog.cc:2:35: note: template argument deduction/substitution failed:
prog.cc:3:12: note: couldn't deduce template parameter 'auto:1'
lambda();
^
为什么会失败?
auto
的类型推导不考虑默认参数。
模板函数(或方法)不会从它们的默认参数推断出它们的类型参数,并且带有 auto
参数的闭包仅仅是一个带有模板方法的对象。
这使得模板函数的默认 lambda 有点烦人。
一种方法是键入擦除调用对象,而不存储它,如下所示:
#include <utility>
#include <type_traits>
#include <memory>
template<class Sig>
struct function_view;
template<class R, class...Args>
struct function_view<R(Args...)>{
void* state;
R(*f)(void*, Args&&...);
template<class F, class=std::enable_if_t<std::is_convertible<std::result_of_t<F&(Args...)>,R>{}>>
function_view( F&& fin ):
state(const_cast<void*>(static_cast<void*>(std::addressof(fin)))),
f( [](void* state, Args&&...args)->R{
F&& f = std::forward<F>(*static_cast<std::decay_t<F>*>(state));
return f(std::forward<Args>(args)...);
})
{}
function_view( R(*fin)(Args...) ):
state(fin),
f( fin?+[](void* state, Args&&...args)->R{
R(*f)(Args...) = static_cast<R(*)(Args...)>(state);
return f(std::forward<Args>(args)...);
}:nullptr)
{}
explicit operator bool(){return f;}
function_view():state(nullptr),f(nullptr){}
function_view(std::nullptr_t):function_view(){}
R operator()(Args...args)const{
return f(state, std::forward<Args>(args)...);
}
};
template<class...Args>
struct function_view<void(Args...)>{
void* state;
void(*f)(void*, Args&&...);
template<class F, class=std::result_of_t<F&(Args...)>>
function_view( F&& fin ):
state(const_cast<void*>(static_cast<void*>(std::addressof(fin)))),
f( [](void* state, Args&&...args){
F&& f = std::forward<F>(*static_cast<std::decay_t<F>*>(state));
f(std::forward<Args>(args)...);
})
{}
function_view( void(*fin)(Args...) ):
state(fin),
f( fin?+[](void* state, Args&&...args){
void(*f)(Args...) = static_cast<void(*)(Args...)>(state);
f(std::forward<Args>(args)...);
}:nullptr)
{}
explicit operator bool(){return f;}
function_view():state(nullptr),f(nullptr){}
function_view(std::nullptr_t):function_view(){}
void operator()(Args...args)const{
f(state, std::forward<Args>(args)...);
}
};
int main() {
auto f = [] (function_view<void()> x=[]{}) {
x();
};
f();
}
因为这只适用于函数指针,而且我在 gcc 内联简单函数指针方面有很好的经验,它对性能的影响可能不如 std::function
。与 std::function
不同的是,不涉及虚拟表或堆分配。
对于非 lambda,您可以这样做:
template<class X=function_view<void()>>
void f( X&& x=[]{} ) {
x();
}
如果你传递是一个参数,它会推导出来,如果你不传递,它就会变成一个空函数。你也可以这样做:
struct do_nothing {
template<class...Args>
void operator()(Args&&...)const{}
};
template<class X=do_nothing>
void f( X&& x=do_nothing{} ) {
x();
}
这可能更容易优化。
由于 lambda 是仿函数的 糖,问题在于模板函数无法在此默认上下文中推导模板参数 (auto
)。
通过考虑这些语句,可以将 lambda 简化为仿函数结构级别:
§5.1.2/3 [expr.prim.lambda]
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type [...]
§5.1.2/5 [expr.prim.lambda]
[...] For a generic lambda, the closure type has a public inline function call operator member template (14.5.2) whose template-parameter-list consists of one invented type
template-parameter for each occurrence of auto in the lambda’s parameter-declaration-clause, in order of appearance. [...]
因此,您的 lambda 类型等同于此函子类型:
struct unnamed
{
template<typename Auto1>
auto operator()(Auto1 = []{})
{
}
};
你的用法相当于:
int main() {
auto lambda = unnamed();
lambda();
}
无法根据 §14.8.2.5/5 [temp.deduct.type] 中指定的上下文推断 Auto1
的类型:
The non-deduced contexts are:
[...]
— A template parameter used in the parameter type of a function parameter that has a default argument
that is being used in the call for which argument deduction is being done.
我在使用最新版本的 clang 和 gcc 时遇到错误,代码如下:
int main() {
auto lambda = [] (auto = [] {}) {};
lambda();
}
Clang 报错:
prog.cc: In function 'int main()':
prog.cc:3:12: error: no match for call to '(main()::<lambda(auto:1)>) ()'
lambda();
^
prog.cc:2:35: note: candidate: template<class auto:1> main()::<lambda(auto:1)>
auto lambda = [] (auto = [] {}) {};
^
prog.cc:2:35: note: template argument deduction/substitution failed:
prog.cc:3:12: note: couldn't deduce template parameter 'auto:1'
lambda();
^
为什么会失败?
auto
的类型推导不考虑默认参数。
模板函数(或方法)不会从它们的默认参数推断出它们的类型参数,并且带有 auto
参数的闭包仅仅是一个带有模板方法的对象。
这使得模板函数的默认 lambda 有点烦人。
一种方法是键入擦除调用对象,而不存储它,如下所示:
#include <utility>
#include <type_traits>
#include <memory>
template<class Sig>
struct function_view;
template<class R, class...Args>
struct function_view<R(Args...)>{
void* state;
R(*f)(void*, Args&&...);
template<class F, class=std::enable_if_t<std::is_convertible<std::result_of_t<F&(Args...)>,R>{}>>
function_view( F&& fin ):
state(const_cast<void*>(static_cast<void*>(std::addressof(fin)))),
f( [](void* state, Args&&...args)->R{
F&& f = std::forward<F>(*static_cast<std::decay_t<F>*>(state));
return f(std::forward<Args>(args)...);
})
{}
function_view( R(*fin)(Args...) ):
state(fin),
f( fin?+[](void* state, Args&&...args)->R{
R(*f)(Args...) = static_cast<R(*)(Args...)>(state);
return f(std::forward<Args>(args)...);
}:nullptr)
{}
explicit operator bool(){return f;}
function_view():state(nullptr),f(nullptr){}
function_view(std::nullptr_t):function_view(){}
R operator()(Args...args)const{
return f(state, std::forward<Args>(args)...);
}
};
template<class...Args>
struct function_view<void(Args...)>{
void* state;
void(*f)(void*, Args&&...);
template<class F, class=std::result_of_t<F&(Args...)>>
function_view( F&& fin ):
state(const_cast<void*>(static_cast<void*>(std::addressof(fin)))),
f( [](void* state, Args&&...args){
F&& f = std::forward<F>(*static_cast<std::decay_t<F>*>(state));
f(std::forward<Args>(args)...);
})
{}
function_view( void(*fin)(Args...) ):
state(fin),
f( fin?+[](void* state, Args&&...args){
void(*f)(Args...) = static_cast<void(*)(Args...)>(state);
f(std::forward<Args>(args)...);
}:nullptr)
{}
explicit operator bool(){return f;}
function_view():state(nullptr),f(nullptr){}
function_view(std::nullptr_t):function_view(){}
void operator()(Args...args)const{
f(state, std::forward<Args>(args)...);
}
};
int main() {
auto f = [] (function_view<void()> x=[]{}) {
x();
};
f();
}
因为这只适用于函数指针,而且我在 gcc 内联简单函数指针方面有很好的经验,它对性能的影响可能不如 std::function
。与 std::function
不同的是,不涉及虚拟表或堆分配。
对于非 lambda,您可以这样做:
template<class X=function_view<void()>>
void f( X&& x=[]{} ) {
x();
}
如果你传递是一个参数,它会推导出来,如果你不传递,它就会变成一个空函数。你也可以这样做:
struct do_nothing {
template<class...Args>
void operator()(Args&&...)const{}
};
template<class X=do_nothing>
void f( X&& x=do_nothing{} ) {
x();
}
这可能更容易优化。
由于 lambda 是仿函数的 糖,问题在于模板函数无法在此默认上下文中推导模板参数 (auto
)。
通过考虑这些语句,可以将 lambda 简化为仿函数结构级别:
§5.1.2/3 [expr.prim.lambda]
The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed non-union class type [...]
§5.1.2/5 [expr.prim.lambda]
[...] For a generic lambda, the closure type has a public inline function call operator member template (14.5.2) whose template-parameter-list consists of one invented type template-parameter for each occurrence of auto in the lambda’s parameter-declaration-clause, in order of appearance. [...]
因此,您的 lambda 类型等同于此函子类型:
struct unnamed
{
template<typename Auto1>
auto operator()(Auto1 = []{})
{
}
};
你的用法相当于:
int main() {
auto lambda = unnamed();
lambda();
}
无法根据 §14.8.2.5/5 [temp.deduct.type] 中指定的上下文推断 Auto1
的类型:
The non-deduced contexts are:
[...]
— A template parameter used in the parameter type of a function parameter that has a default argument that is being used in the call for which argument deduction is being done.