【问题标题】:std::bind on a generic lambda - auto type deduction通用 lambda 上的 std::bind - 自动类型推导
【发布时间】:2017-09-27 07:01:39
【问题描述】:

考虑以下代码:

#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&amp; 而不仅仅是int&amp;

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 传递给可调用对象 左值参数:上面 std::invoke 调用中的参数 vn 是 简单的 arg 并且对应的类型 Vn 是 T cv &,其中 cv 是 与 g 相同的 cv 限定。

但在这种情况下,run_foocv-unqualified。我错过了什么?

【问题讨论】:

  • 如果有帮助,arg 被推导出为int,但因为operator()const,所以存储的varconst
  • @Rakete1111 如果我没记错的话,operator() 有一个不合格的版本,当*thisconst 时会调用const 版本,这就更奇怪了。
  • 在我的实现中我只能看到operator() const,但是是的,这很奇怪。
  • 模板参数推导和替换发生在重载决议之前,所以即使operator() const不会被重载决议选中,替换仍然发生。在这种情况下,替换失败不会出现在直接上下文中,因此这个替换失败一个硬错误。
  • @cpplearner 从实现的角度来看这是一个很好的答案,但标准只是说run_foo() 的效果是run(foo, var),不是吗?我看不到它在哪里说“除非调用 as_const(run)(as_const(foo), as_const(var)) 格式不正确”或类似的内容。

标签: c++ c++14


【解决方案1】:

MWE:

#include <functional>

int main() {
    int i;
    std::bind([] (auto& x) {x = 1;}, i)();
}

[func.bind]/(10.4) 声明传递给 lambda 的参数的 cv 限定符是 bind 的参数的那些,由调用包装器的 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()s 在非const 的版本在 SFINAE 下失败后会退回到const 版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多