C++14: универсальная лямбда с универсальной функцией std::function в качестве члена класса

Рассмотрим этот псевдофрагмент:

class SomeClass
{
public:
    SomeClass()
    {
        if(true)
        {
            fooCall = [](auto a){ cout << a.sayHello(); };
        }
        else
        {
            fooCall = [](auto b){ cout << b.sayHello(); };
        }
    }
private:
    template<typename T>
    std::function<void(T)> fooCall;
};

Мне нужен член класса fooCall, в котором хранится общая лямбда, которая, в свою очередь, назначается в конструкторе.

Компилятор жалуется, что fooCall не может быть шаблонным элементом данных.

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


person Juergen    schedule 16.11.2017    source источник
comment
К сожалению, простого решения нет. Считайте общую лямбду похожей на шаблонную функцию; вы не можете получить его адрес, потому что он еще не создан. Нам нужно будет узнать больше о конкретных случаях использования. Возможно, вы сможете конкретизировать ОП с помощью небольшого рабочего процесса?   -  person AndyG    schedule 16.11.2017
comment
Какую настоящую проблему вы пытаетесь решить?   -  person Barry    schedule 16.11.2017
comment
Я не смог сохранить универсальную лямбду как член класса, но смог использовать ее в конструкторе класса для вызова ее вызова.   -  person Francis Cugler    schedule 18.11.2017


Ответы (3)


Вы не сможете выбирать между двумя общими лямбда-выражениями во время выполнения, поскольку у вас нет конкретной подписи для стирания.

Если вы можете принять решение во время компиляции, вы можете создать шаблон самого класса:

template <typename F>
class SomeClass
{
private:
    F fooCall;

public:
    SomeClass(F&& f) : fooCall{std::move(f)} { }
};

Затем вы можете создать вспомогательную функцию для вывода F:

auto makeSomeClassImpl(std::true_type) 
{
    auto l = [](auto a){ cout << a.sayHello(); };
    return SomeClass<decltype(l)>{std::move(l)};
}

auto makeSomeClassImpl(std::false_type) 
{
    auto l = [](auto b){ cout << b.sayHello(); };
    return SomeClass<decltype(l)>{std::move(l)};
}

template <bool B>
auto makeSomeClass() 
{
    return makeSomeClassImpl(std::bool_constant<B>{});
}
person Vittorio Romeo    schedule 16.11.2017
comment
Спасибо, но условие доступно только во время выполнения :-( - person Juergen; 16.11.2017
comment
@juxeii: Пожалуйста, обновите OP, чтобы отразить это. Нам нужно будет сделать некоторую форму стирания шрифта, чтобы заставить его работать. - person AndyG; 16.11.2017

Мне не удалось сохранить std::function<> как generic lambda в классе напрямую как member. Что я смог сделать, так это специально использовать его в конструкторе класса. Я не уверен на 100%, что это то, чего пытался достичь ОП, но это то, что я смог скомпилировать, построить и запустить с тем, к чему, как я подозреваю, стремился ОП с помощью предоставленного ими кода.

template<class>
class test {
public: // While testing I changed this to public access...
        // Could not get object below to compile, build & run
    /*template<class U = T>
    static std::function<void(U)> fooCall;*/
public:
   test();
};

template<class T>
test<T>::test() {
    // This would not compile, build & run
    // fooCall<T> = []( T t ) { std::cout << t.sayHello(); };

    // Removed the variable within the class as a member and moved it here
    // to local scope of the class's constructor
    std::function<void(T)> fooCall = []( auto a ) { std::cout << a.sayHello(); };
    T t; // created an instance of <Type T>
    fooCall(t); // passed t into fooCall's constructor to invoke the call.
}

struct A {
    std::string sayHello() { return "A say's Hello!\n"; }
};

struct B {
    std::string sayHello() { return "B say's Hello!\n"; }
};


int main() {
    // could not instantiate an object of SomeClass<T> with a member of
    // a std::function<> type that is stored by a type of a generic lambda.

    /*SomeClass<A> someA;
    SomeClass<B> someB;
    someA.foo();
    someB.foo();*/

    // Simply just used the object's constructors to invoke the locally stored lambda within the class's constructor.
    test<A> a;
    test<B> b;

    std::cout << "\nPress any key & enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}

С соответствующими заголовками приведенное выше, как есть, должно компилироваться, собираться и запускаться, давая приведенный ниже вывод (по крайней мере, в MSVS 2017 на 64-разрядной версии Windows 7); Я оставил комментарии, где столкнулся с ошибками и попробовал несколько разных методов для получения рабочего примера, ошибки возникали, как предлагали другие, и я обнаружил еще больше, работая с приведенным выше кодом. То, что я смог скомпилировать, построить и запустить, сводилось к этому простому фрагменту кода без комментариев. Я также добавил еще один простой класс, чтобы показать, что он будет работать с любым типом:

template<class>
class test {
public:
    test();
};

template<class T>
test<T>::test() {
    std::function<void( T )> fooCall = []( auto a ) { std::cout << a.sayHello(); };
    T t;
    fooCall( t );
}

struct A {
    std::string sayHello() { return "A say's Hello!\n"; }
};

struct B {
    std::string sayHello() { return "B say's Hello!\n"; }
};

struct C {    
    int sayHello() { return 100; }
};

int main() {
    test<A> testA;
    test<B> testB;
    test<C> testC;

    std::cout << "\nPress any key & enter to quit." << std::endl;
    char c;
    std::cin >> c;

    return 0;
}

Выход:

A say's Hello!
B say's Hello!
100

Press any key & enter to quit

Я не знаю, поможет ли это ОП прямо или косвенно или нет, но если это так или даже если это не так, это все равно то, к чему они могут вернуться и от чего отталкиваться.

person Francis Cugler    schedule 18.11.2017

вы можете просто использовать класс-шаблон или...
Если вам сойдет с рук использование c++17, вы можете сделать тип fooCall
std::function<void(const std::any&)> и сделать небольшую оболочку для его выполнения.

метод 1: просто используйте класс-шаблон (C++14).
метод 2: кажется, что псевдокод имитирует точно так, как предполагалось в OP (C++17).
метод 3: немного проще и проще в использовании, чем метод 2 (C++17).
метод 4: позволяет изменить значение fooCall (C++17).

  • необходимые заголовки и тестовые структуры для демонстрации:
#include <any> //not required for method 1
#include <string>
#include <utility>
#include <iostream>
#include <functional>

struct typeA {
    constexpr const char * sayHello() const { return "Hello from A\n"; }
};

struct typeB {
    const std::string sayHello() const { return std::string(std::move("Hello from B\n")); }
};
  • метод 1:
template <typename T>
class C {
    const std::function<void(const T&)> fooCall;
public:
    C(): fooCall(std::move([](const T &a) { std::cout << a.sayHello(); })){}

    void execFooCall(const T &arg) {
        fooCall(arg);
    }
};

int main (void) {
    typeA A;
    typeB B;
    C<typeA> c1;
    C<typeB> c2;
    c1.execFooCall(A);
    c2.execFooCall(B);
    return 0;
}
  • метод 2:
bool is_true = true;

class C {
    std::function<void(const std::any&)> fooCall;
public:
    C() {
        if (is_true)
            fooCall = [](const std::any &a) { std::cout << std::any_cast<typeA>(a).sayHello(); };
        else
            fooCall = [](const std::any &a) { std::cout << std::any_cast<typeB>(a).sayHello(); };
    }
    template <typename T>
    void execFooCall(const T &arg) {
        fooCall(std::make_any<const T&>(arg));
    }
};

int main (void) {
    typeA A;
    typeB B;
    C c1;
    is_true = false;
    C c2;
    c1.execFooCall(A);
    c2.execFooCall(B);
    return 0;
}
  • метод 3:
/*Note that this very closely resembles method 1. However, we're going to 
  build off of this method for method 4 using std::any*/
template <typename T>
class C {
    const std::function<void(const std::any&)> fooCall;
public:
    C() : fooCall(std::move([](const std::any &a) { std::cout << std::any_cast<T>(a).sayHello(); })) {}

    void execFooCall(const T &arg) {
        fooCall(std::make_any<const T&>(arg));
    }
};

int main (void) {
    typeA A;
    typeB B;
    C<typeA> c1;
    C<typeB> c2;
    c1.execFooCall(A);
    c2.execFooCall(B);
    return 0;
}
  • метод 4:
/*by setting fooCall outside of the constructor we can make C a regular class 
  instead of a templated one, this also complies with the rule of zero.
  Now, we can change the value of fooCall whenever we want.
  This will also allow us to do things like create a container that stores
  a vector or map of functions that each take different parameter types*/
class C {
    std::function<void(const std::any&)> fooCall; //could easily be replaced by a vector or map
public:
    /*could easily adapt this to take a function as a parameter so we can change
      the entire body of the function*/
    template<typename T>
    void setFooCall() {
        fooCall = [](const std::any &a) { std::cout << std::any_cast<T>(a).sayHello(); };
    }

    template <typename T>
    void execFooCall(const T &arg) {
            fooCall(std::make_any<const T&>(arg));
    }
};

int main (void) {
    typeA A;
    typeB B;
    C c;
    c.setFooCall<typeA>;
    c.execFooCall(A);
    c.setFooCall<typeB>;
    c.execFooCall(B);
    return 0;
}
  • Выход из любого метода
Hello from A
Hello from B
person pseudovella    schedule 04.03.2021