C ++ с использованием захвата лямбда внутри виртуального метода - PullRequest
1 голос
/ 11 марта 2019

Я пытался использовать следующий код C ++, но он не компилируется с ошибкой no matching function for call to ... для вызова z внутри bar::s при компиляции с g ++ 4.9.2 и --std=c++11:

template <class T>
class foo {
    public:
        virtual void s( T scale , const foo<T>& rhs ) {
        }
};

template <class T>
class bar : public foo<T> {
    public:
        T z( T init , T (*f)( T a , T& x , const T& y ) , const foo<T>& rhs ) {
        }
        void s( T scale , const foo<T>& rhs ) {
            this->z( ((T)0) , [=]( T a, T& l, const T& r ) {return ((l+=scale*r)?((T)0):((T)0));} , rhs );
        }
};

int main () {
    bar<float>* a = new bar<float>();
    foo<float>* b = new foo<float>();
    return 0;
}

Код компилируется, если я удаляю virtual перед определением метода, а также если я удаляю захваченный scale из лямбды. Если правильно понять проблему, она работает следующим образом: когда метод не является виртуальным, лямбда с захватом используется через operator () его связанного типа. Когда метод является виртуальным, этот оператор не может использоваться, потому что он не может быть вызван с помощью динамической диспетчеризации, только с помощью обычного вызова метода. Но лямбда также не может быть преобразована в указатель на функцию, потому что это преобразование доступно только для лямбд без захвата. И я предполагаю, что z вызывается посредством динамической отправки, если s является виртуальным, даже если z не является виртуальным. Этот анализ правильный?

Я не понимаю, почему z нельзя вызвать с помощью обычного вызова метода - в конце концов, он не виртуальный. Я также не понимаю, почему удаление параметров шаблона и замена всех T s на float s приводит к сбою компиляции как в виртуальном, так и в не виртуальном случае (удаление захваченного scale по-прежнему позволяет выполнить успешную компиляцию ).

В любом случае, существует ли простой и несколько эффективный способ исправить это? Я хотел бы использовать лямбды с перехватами (или чем-то подобным) как в виртуальных, так и в не виртуальных методах шаблонных классов. Я знаю о std::function, но он довольно тяжелый. А лямбды без захвата немного менее мощные, чем лямбды с захватами, поэтому они, вероятно, не смогут решить мои проблемы.

PS: Если вы хотите знать, что должен делать z: это комбинация того, что будет zipWith с последующим foldl в Haskell (или что-то вроде mapreduce с двумя списками ввода, если вы не не знаю Haskell) и может использоваться для большинства унарных и двоичных операций с использованием foo s. В моей первоначальной реализации init не был типа T, а вместо этого использовал в качестве типа дополнительный параметр шаблона, который был удален здесь для простоты.

Ответы [ 2 ]

2 голосов
/ 11 марта 2019

Используйте шаблон.

virtual здесь на самом деле не имеет ничего общего с вашей проблемой.Проблема в том, что необработанные указатели на функции не могут содержать с собой никакого состояния, и поэтому лямбды с перехватами не могут быть преобразованы в указатели на функции.

Вместо необработанного указателя на функцию вы должны сделать z aшаблон, который принимает любой тип в качестве второго аргумента:

template <class T>
class foo {
    public:
        virtual void s( T scale , const foo<T>& rhs ) {
        }
};

template <class T>
class bar : public foo<T> {
    public:
        template <typename F>
        T z( T init , F&& f, const foo<T>& rhs ) {
            f(/*some args*/);
        }
        void s( T scale , const foo<T>& rhs ) {
            this->z( ((T)0) , [=]( T a, T& l, const T& r ) {return ((l+=scale*r)?((T)0):((T)0));} , rhs );
        }
};

int main () {
    bar<float>* a = new bar<float>();
    foo<float>* b = new foo<float>();
    return 0;
}

Live Demo

Теперь, когда z принимает вашу лямбду по ее фактическому типу, ее состояние может бытьпродолжил, и все работает.

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

Как отметил Рэймонд Чен, виртуальность - это действительно красная сельдь.

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

template <class T>
class foo {
    public:
        virtual void s( T scale , const foo<T>& rhs ) {
        }
};

template <class T>
class bar : public foo<T> {
    public:
        T z( T init , T (*f)( T a , T& x , const T& y ) , const foo<T>& rhs ) {
        }
        template <class U>
        U z2( U init , T capture_0 , T capture_1 , U (*f)( T capture_0 , T capture_1 , U a , T& x , const T& y ) , const foo<T>& rhs ) {
            return init;
        }
        void s( T scale , const foo<T>& rhs ) {
            this->z2<T>( ((T)0) , scale , ((T)0) , []( T c0 , T c1 , T a, T& l, const T& r ) {return ((l+=c0*r)?((T)0):((T)0));} , rhs );
            //this->z( ((T)0) , [=]( T a, T& l, const T& r ) {return ((l+=scale*r)?((T)0):((T)0));} , rhs );
        }
};

int main () {
    bar<float>* a = new bar<float>();
    foo<float>* b = new foo<float>();
    a->s(0,*b);
    return 0;
}

Это компилирует и позволяет мне использовать z2 с до 2 "псевдо-захватами" правильного типа, с виртуальностью или без нее и даже с дополнительным параметром шаблона, который яоставлено в вопросе.Ненужные захваты можно просто установить на 0 и игнорировать.

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