Сильная ошибка при использовании std :: invoke_result_t с общей лямбда - PullRequest
0 голосов
/ 16 ноября 2018

У меня есть контейнероподобный класс с методом, который работает аналогично std::apply.Я хотел бы перегрузить этот метод квалификатором const, однако, когда я пытаюсь вызвать этот метод с помощью общей лямбды, я получаю серьезную ошибку от создания экземпляра std::invoke_result_t.Я использую std::invoke_result_t для вывода возвращаемого значения метода, а также для проверки SFINAE аргумента.

#include <type_traits>
#include <utility>

template <typename T>
class Container
{
public:
    template <typename F>
    std::invoke_result_t<F, T &> apply(F &&f)
    {
        T dummyValue;
        return std::forward<F>(f)(dummyValue);
    }

    template <typename F>
    std::invoke_result_t<F, const T &> apply(F &&f) const
    {
        const T dummyValue;
        return std::forward<F>(f)(dummyValue);
    }
};

int main()
{
    Container<int> c;
    c.apply([](auto &&value) {
        ++value;
    });
    return 0;
}

Сообщение об ошибке при компиляции с Clang 6.0:

main.cc:27:9: error: cannot assign to variable 'value' with const-qualified type 'const int &&'
        ++value;
        ^ ~~~~~
type_traits:2428:7: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<const int &>' requested here
      std::declval<_Fn>()(std::declval<_Args>()...)
      ^
type_traits:2439:24: note: while substituting deduced template arguments into function template '_S_test' [with _Fn = (lambda at main.cc:26:13), _Args = (no value)]
      typedef decltype(_S_test<_Functor, _ArgTypes...>(0)) type;
                       ^
type_traits:2445:14: note: in instantiation of template class 'std::__result_of_impl<false, false, (lambda at main.cc:26:13), const int &>' requested here
    : public __result_of_impl<
             ^
type_traits:2831:14: note: in instantiation of template class 'std::__invoke_result<(lambda at main.cc:26:13), const int &>' requested here
    : public __invoke_result<_Functor, _ArgTypes...>
             ^
type_traits:2836:5: note: in instantiation of template class 'std::invoke_result<(lambda at main.cc:26:13), const int &>' requested here
    using invoke_result_t = typename invoke_result<_Fn, _Args...>::type;
    ^
main.cc:16:10: note: in instantiation of template type alias 'invoke_result_t' requested here
    std::invoke_result_t<F, const T &> apply(F &&f) const
         ^
main.cc:26:7: note: while substituting deduced template arguments into function template 'apply' [with F = (lambda at main.cc:26:13)]
    c.apply([](auto &&value) {
      ^
main.cc:26:23: note: variable 'value' declared const here
    c.apply([](auto &&value) {
               ~~~~~~~^~~~~

Я не уверен, является ли std::invoke_result_t дружественным по отношению к SFINAE, но я не думаю, что в этом проблема, поскольку я пытался заменить его типом конечного возврата, например:

auto apply(F &&f) const -> decltype(std::declval<F>()(std::declval<const T &>()))

и получил похожую ошибку:

main.cc:27:9: error: cannot assign to variable 'value' with const-qualified type 'const int &&'
        ++value;
        ^ ~~~~~
main.cc:16:41: note: in instantiation of function template specialization 'main()::(anonymous class)::operator()<const int &>' requested here
    auto apply(F &&f) const -> decltype(std::declval<F>()(std::declval<const T &>()))
                                        ^
main.cc:26:7: note: while substituting deduced template arguments into function template 'apply' [with F = (lambda at main.cc:26:13)]
    c.apply([](auto &&value) {
      ^
main.cc:26:23: note: variable 'value' declared const here
    c.apply([](auto &&value) {
               ~~~~~~~^~~~~

Вопросы:

  1. Почему это происходит?Точнее, почему тело лямбды создается во время, как кажется, разрешения перегрузки?
  2. Как мне обойти это?

Ответы [ 2 ]

0 голосов
/ 16 ноября 2018

Так что разрешение перегрузки здесь немного тупо.

Это не говорит: «Хорошо, если не-1003 * apply работает, я никогда не позвоню const apply, поэтому я не буду беспокоиться об этом».

Вместо этого разрешение перегрузки обнуляет каждого возможного кандидата. Затем он устраняет те, которые терпят неудачу замещения. Только тогда он заказывает кандидатов и выбирает одного.

Таким образом, оба из них F подставляются в них:

template <typename F>
std::invoke_result_t<F, T &> apply(F &&f)

template <typename F>
std::invoke_result_t<F, const T &> apply(F &&f) const

Я снял их тела.

Теперь, что происходит, когда вы передаете лямбда-тип F этим?

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

И SFINAE не работает при проверке органов функций (или лямбд). Это сделано для того, чтобы упростить работу компилятора (поскольку SFINAE очень сложен для компиляторов, поскольку им приходится компилировать произвольный код и сталкиваться с произвольными ошибками, а затем откатывать его назад - огромный барьер).

Мы можем избежать мгновенного образования лямбда-тела с помощью этого:

[](auto &&value) -> void { /* ... */ }

после этого обе перегрузки apply:

template <typename F>
std::invoke_result_t<F, T &> apply(F &&f)

template <typename F>
std::invoke_result_t<F, const T &> apply(F &&f) const

может иметь оценочное возвращаемое значение (это просто void), и мы получаем:

template <typename F=$lambda$>
void apply(F &&f)

template <typename F=$lambda$>
void apply(F &&f) const

Теперь обратите внимание, что apply const все еще существует. Если вы позвоните apply const, вы получите серьезную ошибку, вызванную созданием этого лямбда-тела.

Если вы хотите, чтобы сама лямбда была дружественной по отношению к SFINAE, вам нужно сделать следующее:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

[](auto &&value) RETURNS(++value)

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

[](auto &&value) RETURNS((void)++value)

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

Побочным эффектом этого является то, что применение не const теперь исключено из разрешения перегрузки SFINAE. Что делает его дружелюбным по отношению к СФИНАМ.

Было предложение взять RETURNS и переименовать его =>, но в последний раз я проверял, что он не был принят для .

0 голосов
/ 16 ноября 2018

Лямбды выводят тип возврата, если вы не укажете тип возврата явно. Таким образом, std::invoke_result_t должен создать экземпляр тела, чтобы определить тип возвращаемого значения. Этот экземпляр не в непосредственном контексте и вызывает серьезную ошибку.

Вы можете скомпилировать свой код, написав:

[](auto &&value) -> void { /* ... */ }

Здесь тело лямбды не будет создано, пока тело apply не окажется в чистоте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...