Почему std :: is_invocable не может обрабатывать пересылку? - PullRequest
7 голосов
/ 20 марта 2020

У меня есть класс, который просто перенаправляет вызов функции в другой класс, и я хотел бы иметь возможность использовать std::invocable<> в моем классе пересылки. Но по какой-то причине это не удается ... Это то, что я должен ожидать? Есть ли способ обойти это?

#include <type_traits>
#include <utility>

struct Foo {
    constexpr int operator()( int i ) const {
        return i;
    }
};

struct ForwardToFoo {
    template<class ...Args>
    constexpr decltype(auto) operator()( Args &&...args ) const {
        Foo foo;
        return foo( std::forward<Args>( args )... );
    }
};

int main( void ) {
    // These work fine
    static_assert( std::is_invocable_v<ForwardToFoo, int> == true );
    static_assert( std::is_invocable_v<Foo, int> == true );
    static_assert( std::is_invocable_v<Foo> == false );

    // This causes a compile error
    static_assert( std::is_invocable_v<ForwardToFoo> == false );

    return 0;
}

Редактировать: Ответы до сих пор предполагают, что проблема в том, что последний static_assert() заставляет ForwardToFoo::operator()<> быть создан без аргументов следовательно вызывает ошибку компиляции. Так есть ли способ превратить эту ошибку создания в ошибку SFINAE, которая может быть обработана без ошибки компиляции?

Ответы [ 3 ]

6 голосов
/ 20 марта 2020

Вы получаете ту же ошибку, что и от

ForwardToFoo{}();

, у вас есть то, что operator() в ForwardToFoo вызывается без аргументов. Но когда он вызывает оператор в Foo() без аргументов ... вы получаете ошибку.

Есть ли способ обойти это?

Да: вы можете включить SFINAE ForwardToFoo()::operator() только тогда, когда Foo()::operator() вызывается с аргументами.

Я имею в виду ... вы можете написать ForwardToFoo()::operator() следующим образом

template<class ...Args>
constexpr auto operator()( Args &&...args ) const
   -> decltype( std::declval<Foo>()(std::forward<Args>(args)...) ) 
 { return Foo{}( std::forward<Args>( args )... ); }

- - РЕДАКТИРОВАТЬ -

Джефф Гаррет отмечает важный момент, который я пропустил.

Вообще говоря, простое использование std::invokable не вызывает создание экземпляра вызываемого в первом аргумент.

Но в данном конкретном случае тип возвращаемого значения ForwardToFoo::operator() равен decltype(auto). Это вынуждает компилятор обнаруживать возвращаемый тип, и это приводит к созданию экземпляра и ошибке.

Контрпример: если вы пишете оператор как void функцию, которая вызывает Foo{}(), переадресация аргументов, но не возвращение значение

template <typename ... Args>
constexpr void operator() ( Args && ... args ) const
 { Foo{}( std::forward<Args>( args )... ); }

теперь компилятор знает, что возвращаемый тип void без его создания.

Вы также получаете ошибку компиляции из

static_assert( std::is_invocable_v<ForwardToFoo> == false );

но это время объясняется тем, что ForwardToFoo{}() результат вызывается без аргументов.

Если вы напишите

static_assert( std::is_invocable_v<ForwardToFoo> == true );

, ошибка исчезнет.

Оставьте истину, что

ForwardToFoo{}();

выдает ошибку компиляции, потому что это создает экземпляр оператора.

1 голос
/ 20 марта 2020

Я не могу понять, почему вы ожидали, что это сработает.

Foo требует, чтобы int вызывался, так что ForwardToFoo тоже. В противном случае его вызов Foo будет некорректным.

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

Подумайте, как бы вы вызвали ForwardWithFoo. Не могли бы вы сделать это без аргументов? Что будет?

0 голосов
/ 20 марта 2020

Проблема не связана с пересылкой или нет. В вашем последнем static_assert вы запрашиваете у компилятора ForwardToFoo::operator() без аргументов. Внутри этого оператора вы звоните Foo::operator(), который имеет только перегрузку, принимая int. Это, очевидно, вызывает серьезную ошибку компилятора.

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

Потенциальным исправлением в вашем коде может быть принудительная передача хотя бы одного параметра в ForwardToFoo::operator():

struct ForwardToFoo {
    template<class Arg0, class ...Args>
    constexpr decltype(auto) operator()( Arg0 arg0, Args ...args ) const {
        Foo foo;
        return foo(arg0, args...);

    }
};

Тогда компилятор не будет создавать экземпляр этого оператора (поскольку он не является может вызываться без параметра).

Или вы можете использовать выражение-sfinae, как показано в другом ответе.

См. живой пример

...