Как разделить статическую лямбду в объявлении и определении? - PullRequest
0 голосов
/ 27 февраля 2019

У меня есть статическая лямбда внутри класса, которая объявлена ​​и определена в заголовочном файле как:

class A final {
    // inline could be removed of course for splitting
    static inline auto foo = [](auto& param1, auto& param2 auto& param3) {
        // do stuff
        return;
    }
}
// compiles fine

Со статическими переменными, такими как static int x = 42, я могу разделить объявление и определение следующим образом:

// bar.h:
class Bar {
    static int x;
}
// bar.cpp:
#include "bar.h"
int Bar::x = 42;

Как можно архивировать то же самое с помощью вышеприведенной лямбды?Смена подписи, конечно, будет в порядке.

Ответы [ 3 ]

0 голосов
/ 27 февраля 2019

Как можно архивировать то же самое с помощью вышеприведенной лямбды?

Вы не можете.Вы должны всегда объявлять тип переменной.Если вы определяете лямбду после объявления переменной, тогда объявленный тип не может быть выведен из инициализатора.Но вы не можете ссылаться на тип лямбда-выражения до того, как он был определен, потому что тип является анонимным.

Вместо лямбда-выражения (т. Е. Объекта анонимной функции) вы можете просто использовать функциональный объект именованного типа.,Затем вы можете разделить определение функции.Кроме того, вы должны объявить тип возвращаемого значения функции, поскольку он не может быть выведен без определения функции:

class A final {
    constexpr struct Foo {
        template<class Param1, class Param2, class Param3>
        void operator()(Param1&, Param2&, Param3&) const;
    } foo{};
};

Однако, как вы можете заметить, функция на самом деле является шаблоном функции.Это потому, что вы используете auto аргументы в лямбда-выражении.Если вы хотите определить шаблон вне заголовка, то вы должны ограничить экземпляры ограниченным набором и явно создать экземпляры, для которых определен шаблон:

// the definition
template<class Param1, class Param2, class Param3>
void A::Foo::operator()(Param1&, Param2&, Param3&) const {
    // do stuff
    return;
}

// explicit instantiations
template void A::Foo::operator()<int, int, int>(int&, int&, int&) const;
template void A::Foo::operator()<int, double, float>(int&, double&, float&) const;

Если вы попытаетесь вызвать с аргументами, у которых нетне было создано, в модуле перевода, где шаблон не определен, возникнет ошибка компоновщика.

Если вы хотите оставить аргументы без ограничений, то у вас конфликтующие требования.Неограниченные шаблоны могут быть достигнуты только путем определения шаблона в заголовке.


С другой стороны, вы можете решить, нужен ли вам в первую очередь функциональный объект.Вы не продемонстрировали свою потребность в этом.Приведенный выше пример работает так же хорошо, если бы вы сделали его статической функцией-членом (шаблоном) вместо функционального объекта.

0 голосов
/ 27 февраля 2019

Вы все еще можете создать функтор по-старому:

struct Foo
{
    template <typename T1, typename T2, typename T3>
    void operator ()(T1& param1, T2& param2, T3& param3) const;
};

template <typename T1, typename T2, typename T3>
void Foo::operator ()(T1& param1, T2& param2, T3& param3) const
{
    /*..*/
}

class A final {
    // inline could be removed of course for splitting
    static const Foo foo;
};

// in .cpp
const Foo A::foo{};
0 голосов
/ 27 февраля 2019

Основная проблема заключается в том, что каждое лямбда-выражение имеет свой отдельный тип ( см. Также этот ответ ).Поскольку вам нужно знать тип для объявления переменной, и вам нужно знать лямбда-выражение, чтобы знать его тип, нет способа объявить переменную для хранения результата лямбда-выражения, не зная самого лямбда-выражения.

Можно объявить переменную для хранения лямбды и определить переменную отдельно, если вы знаете лямбда-выражение в обоих местах.Например:

inline auto makeFoo() {
    return [](auto& param1, auto& param2, auto& param3) {
        // do stuff
        return;
    };
}

class A final {
    static decltype(makeFoo()) foo;
};

decltype(makeFoo()) A::foo = makeFoo();

Однако невозможно отделить объявление переменной от лямбда-выражения (т.е. вы не можете иметь лямбда-выражение только в файле, в котором вы поместили определениепеременная).

Лямбда-выражение, которое ничего не захватывает, может быть преобразовано в указатель на функцию.Если вам не нужна ваша лямбда для захвата чего-либо (как в вашем примере) и вам нужен только вызываемый элемент для одной конкретной сигнатуры, вы можете просто объявить A::foo как тип указателя на функцию и инициализировать определение A::foo с помощьюсоответствие лямбда:

class A final {
    static void (*foo)(int&, float&, double&);
};

void (*A::foo)(int&, float&, double&) = [](auto& param1, auto& param2, auto& param3) {
    // do stuff
    return;
};

Если вам действительно нужна общая лямбда (такая, с параметрами auto), как предложено в вашем примере, это тоже не сработает, и вам, скорее всего, не повезло.Наличие общего оператора вызова означает, что функция operator () вашего типа закрытия должна быть шаблоном функции.Наличие шаблона функции operator () означает, что его определение должно быть известно любому, кто действительно совершит вызов.Даже если бы вы написали свой собственный класс вместо использования лямбда-выражения, никто бы не вызвал обобщенный operator (), не зная его определения.Все, что вы можете сделать, это объявить явные экземпляры шаблона operator () для всех подписей, которые вам нужно поддерживать, и определить их отдельно.Но это, опять же, требует, чтобы вы заранее знали, какие конкретные подписи вам нужны, чтобы их вызывали для поддержки ...

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