Почему лямбда-авто и параметр выбирают постоянную перегрузку? - PullRequest
0 голосов
/ 01 марта 2019

Я пытаюсь реализовать класс, который оборачивает произвольный тип и мьютекс.Чтобы получить доступ к упакованным данным, необходимо передать объект функции в качестве параметра метода locked.Затем класс-обертка будет передавать обернутые данные в качестве параметра этому объекту функции.

Я бы хотел, чтобы мой класс-обертка работал с const & non-const, поэтому я попробовал следующее

#include <mutex>
#include <string>

template<typename T, typename Mutex = std::mutex>
class   Mutexed
{
private:
    T m_data;
    mutable Mutex m_mutex;

public:
    using type = T;
    using mutex_type = Mutex;

public:
    explicit Mutexed() = default;

    template<typename... Args>
    explicit Mutexed(Args&&... args)
        : m_data{std::forward<Args>(args)...}
    {}

    template<typename F>
    auto locked(F&& f) -> decltype(std::forward<F>(f)(m_data)) {
        std::lock_guard<Mutex> lock(m_mutex);
        return std::forward<F>(f)(m_data);
    }

    template<typename F>
    auto locked(F&& f) const -> decltype(std::forward<F>(f)(m_data)) {
        std::lock_guard<Mutex> lock(m_mutex);
        return std::forward<F>(f)(m_data);
    }
};

int main()
{
    Mutexed<std::string> str{"Foo"};

    str.locked([](auto &s) { /* this doesn't compile */
        s = "Bar";
    });

    str.locked([](std::string& s) { /* this compiles fine */
        s = "Baz";
    });
    return 0;
}

Первый locked вызов с общей лямбдой не может скомпилироваться со следующей ошибкой

/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp: In instantiation of ‘main()::<lambda(auto:1&)> [with auto:1 = const std::__cxx11::basic_string<char>]’:
/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:30:60:   required by substitution of ‘template<class F> decltype (forward<F>(f)(((const Mutexed<T, Mutex>*)this)->Mutexed<T, Mutex>::m_data)) Mutexed<T, Mutex>::locked(F&&) const [with F = main()::<lambda(auto:1&)>]’
/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:42:6:   required from here
/home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:41:11: error: passing ‘const std::__cxx11::basic_string<char>’ as ‘this’ argument discards qualifiers [-fpermissive]
         s = "Bar";
           ^
In file included from /usr/include/c++/5/string:52:0,
                 from /usr/include/c++/5/stdexcept:39,
                 from /usr/include/c++/5/array:38,
                 from /usr/include/c++/5/tuple:39,
                 from /usr/include/c++/5/mutex:38,
                 from /home/foo/tests/lamdba_auto_const/lambda_auto_const/main.cpp:1:
/usr/include/c++/5/bits/basic_string.h:558:7: note:   in call to ‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::operator=(const _CharT*) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’
       operator=(const _CharT* __s)
       ^

Но второй вызов с параметром std::string& в порядке.

Почему это так??И есть ли способ заставить его работать должным образом при использовании универсальной лямбды?

1 Ответ

0 голосов
/ 01 марта 2019

Это принципиальная проблема с тем, что происходит с недружелюбными вызовами SFINAE.Для получения дополнительной информации, проверьте P0826 .

Проблема в том, что когда вы вызываете это:

 str.locked([](auto &s) { s = "Bar"; });

У нас есть две перегрузки locked, и мы должны попробовать оба.Перегрузка non-const работает нормально.Но const one - даже если он все равно не будет выбран разрешением перегрузки - все равно нужно создать его экземпляр (это общая лямбда, поэтому, чтобы выяснить, что может быть decltype(std::forward<F>(f)(m_data)), нужно создать его экземпляр) и этот экземплярне в теле лямбды.Тело находится вне непосредственного контекста, поэтому это не ошибка замещения - это серьезная ошибка.

Когда вы вызываете это:

str.locked([](std::string& s) { s = "Bar"; });

Нам не нужно смотреть наbody вообще во время всего процесса разрешения перегрузки - мы можем просто отклонить на сайте вызова (поскольку вы не можете передать const string в string&).

На самом деле не существует решения для этогопроблема в современном языке - вам нужно добавить ограничения на лямбду, чтобы сбой создания экземпляра происходил в непосредственном контексте замещения, а не в теле.Что-то вроде:

str.locked([](auto &s) -> void {
    s = "Bar";
});

Обратите внимание, что нам не нужно делать этот SFINAE-дружественным - нам просто нужно убедиться, что мы можем определить тип возвращаемого значения без создания экземпляра тела.

Более полное языковое решение состояло бы в том, чтобы учесть "Вывод this" (см. раздел в статье об этой конкретной проблеме).Но этого не будет в C ++ 20.

...