请考虑以下代码:
#include <iostream>
#include <functional>
int main() {
auto run = [](auto&& f, auto&& arg) {
f(std::forward<decltype(arg)>(arg));
};
auto foo = [](int &x) {};
int var;
auto run_foo = std::bind(run, foo, var);
run_foo();
return 0;
}
使用clang编译时出现以下编译错误:
$ clang++ -std=c++14 my_test.cpp
my_test.cpp:6:9: error: no matching function for call to object of type 'const (lambda at my_test.cpp:8:16)'
f(std::forward<decltype(arg)>(arg));
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/6.3.1/../../../../include/c++/6.3.1/functional:998:14: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<const (lambda at my_test.cpp:8:16) &, const int &>' requested here
= decltype( std::declval<typename enable_if<(sizeof...(_Args) >= 0),
^
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/6.3.1/../../../../include/c++/6.3.1/functional:1003:2: note: in instantiation of default argument for 'operator()<>' required here
operator()(_Args&&... __args) const
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
my_test.cpp:11:12: note: while substituting deduced template arguments into function template 'operator()' [with _Args = <>, _Result = (no value)]
run_foo();
^
my_test.cpp:8:16: note: candidate function not viable: 1st argument ('const int') would lose const qualifier
auto foo = [](int &x) {};
^
my_test.cpp:8:16: note: conversion candidate of type 'void (*)(int &)'
1 error generated.
为什么arg
推断为const int&
而不仅仅是int&
?
std :: bind documentation说:
给定一个从之前调用bind获得的对象g,当它是 在函数调用表达式g(u1,u2,... uM)中调用,一个调用 存储对象的发生,就像通过std :: invoke(fd, std :: forward(v1),std :: forward(v2),..., std :: forward(vN)),其中fd是std :: decay_t类型的值 确定绑定参数v1,v2,...,vN的值和类型 如下所述。
...
否则, 普通存储的参数arg作为传递给invokable对象 lvalue参数:上面的std :: invoke调用中的参数vn是 简单的arg和相应的类型Vn是T cv&amp;,其中cv是 与g的相同的cv资格
但在这种情况下,run_foo
cv-unqualified 。我错过了什么?
答案 0 :(得分:8)
MWE:
#include <functional>
int main() {
int i;
std::bind([] (auto& x) {x = 1;}, i)();
}
[func.bind]/(10.4)表示传递给lambda的参数的cv限定符是bind
的参数的cv限定符,由调用包装器的cv限定符扩充;但是没有,因此应该传入非const
int
。
libc ++和libstdc ++都无法解析调用。对于libc ++,报告为#32856,libstdc ++报告为#80564。主要问题是两个库都以某种方式推断签名中的返回类型,对于libstdc ++看起来像这样:
// Call as const
template<typename... _Args, typename _Result
= decltype( std::declval<typename enable_if<(sizeof...(_Args) >= 0),
typename add_const<_Functor>::type&>::type>()(
_Mu<_Bound_args>()( std::declval<const _Bound_args&>(),
std::declval<tuple<_Args...>&>() )... ) )>
_Result operator()(_Args&&... __args) const
在重载解析所必需的模板参数推导期间,将实例化默认模板参数,这会因闭包内部形式错误而导致硬错误。
这可以通过推断的占位符来修复:完全删除_Result
及其默认参数,并将返回类型声明为decltype(auto)
。这样,我们也摆脱了影响重载分辨率的SFINAE,从而导致不正确的行为:
#include <functional>
#include <type_traits>
struct A {
template <typename T>
std::enable_if_t<std::is_const<T>{}> operator()(T&) const;
};
int main() {
int i;
std::bind(A{}, i)();
}
这不应该编译 - 如上所述,传递给A::operator()
的参数应该是非const
因为i
和转发调用包装器。但是,再次,这在libc ++和libstdc ++下进行编译,因为在非operator()
个版本在SFINAE下失败后,const
版本会回退到const
个版本。