std :: bind在泛型lambda上 - 自动类型推导

Kin*_*ini 24 c++ c++14

请考虑以下代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

使用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.
Run Code Online (Sandbox Code Playgroud)

为什么arg推断出const int&而不仅仅是int&?

std :: bind 文档说:

给定从先前调用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作为lvalue参数传递给invokable对象:上面的std :: invoke调用中的参数vn只是arg,相应的类型Vn是T cv&,其中cv与cv资格相同那个.

但在这种情况下,run_foo是不合格的.我错过了什么?

Col*_*mbo 8

MWE:

#include <functional>

int main() {
    int i;
    std::bind([] (auto& x) {x = 1;}, i)();
}
Run Code Online (Sandbox Code Playgroud)

[func.bind] /(10.4)声明传递给lambda的参数bind的cv限定符是参数的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 
Run Code Online (Sandbox Code Playgroud)

在重载解析所必需的模板参数推导期间,将实例化默认模板参数,这会导致由于我们在闭包内部形式错误而导致的硬错误.

这可以通过推导的占位符来修复:_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)();
} 
Run Code Online (Sandbox Code Playgroud)

这不应该编译 - 如上所述,传递给的参数 A::operator()应该是非const因为i转发调用包装器.然而,再次,这在libc ++和libstdc ++下进行编译,因为在SFINAE下非版本失败后,它们会operator()回落到const版本上const.