Подтвердить что ты не робот

Std:: bind на общей лямбде - автоматический вывод типа

Рассмотрим следующий код:

#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 документация говорит:

Учитывая объект g, полученный из более раннего вызова bind, когда он вызывается в выражении функции 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: аргумент vn в вызове std:: invoke выше просто arg и соответствующий тип Vn является T cv &, где cv - такая же cv-квалификация, как и у g.

Но в этом случае run_foo cv-unqualified. Что мне не хватает?

4b9b3361

Ответ 1

MWE:

#include <functional>

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

[func.bind]/(10.4) утверждает, что cv-квалификаторы аргумента, переданного лямбда, относятся к аргументу аргумента bind, дополненный cv-квалификаторами оболочки вызова; но их нет, и поэтому необходимо передать не const int.

Оба libС++ и libstdС++ не могут разрешить вызов. Для libС++, как # 32856, libstdС++ как # 80564, Основная проблема заключается в том, что обе библиотеки выводят тип возврата в подписи как-то, похожий на libstdС++:

  // 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 и оболочка переадресации вызовов. Однако, опять же, это компилируется под libС++ и libstdС++, потому что их operator() возвращаются в версии const после того, как те, которые не были const, терпят неудачу под SFINAE.