vabuff
vabuff

Reputation: 73

Is it possible to override virtual functions with external friend lambda functions?

I am writing some game code. I have an abstract game object class, and I want to make many various instances of the class. I want to write them quickly, without writing subclasses for each of them, so I tried to override the virtual functions with external friend lambda functions. Here is some simplified code:

#include <iostream>
#include <memory>

class B {
public:
    virtual ~B() = default;
    virtual void g() = 0;
protected:
    int x_ = 42;
};

template<typename F>
class C : public B {
public:
    C(F f) : f_(f) {}
    void g() override { f_(*this); }
private:
    F f_;
    friend decltype(f_);
};

template<typename F>
std::unique_ptr<B> make_c(F f) {
    return std::make_unique<C<F>>(f);
}

int main() {
    std::unique_ptr<B> c = make_c([](auto& self) {
        std::cout << self.x_ << "\n";
    });
    c->g();

    return 0;
}

I tried this code with Clang and GCC. Clang compiles it, but GCC dies with an internal compiler error:

I tried changing the argument type of the lambda from auto& to B&. Then, neither GCC and Clang will compile it, saying "int B::x_ is protected within this context".

Is my code valid? If not, do some alternative methods exist?

Why does Clang compile it while GCC fails?

Upvotes: 7

Views: 1156

Answers (1)

Aconcagua
Aconcagua

Reputation: 25536

I do not really see how to get around the limitation of friend just with C++ means. However consider the following:

int main(int argc, char* argv[])
{
    class : public B
    {
    public:
        virtual void g() override { std::cout << x_ << "\n"; }
    } c;
    c.g();

    return 0;
}

Anonymous class with single instance. Sure, not yet what you are after, but it can be the base for some macro magic...

#define MAKE_GENERIC_B(INSTANCE_NAME, IMPLEMENTATION) \
class : public B                                      \
{                                                     \
public:                                               \
    virtual void g() IMPLEMENTATION                   \
} INSTANCE_NAME

int main(int argc, char* argv[])
{
    MAKE_GENERIC_B(c,                             \
            {                                     \
                    char a[12] = { };             \
                    char d[10] = { };             \
                    memcpy(a, d, sizeof(d));      \
                    std::cout << x_ << std::endl; \
            }                                     \
    );
    c.g();
    return 0;
}

Suppose that comes quite close to what you are after. Multi-line implementations as illustrated come with quite ugly line endings, though, but if you have many short implementations that fit into a single line, it might be interesting...

Edit: Using std::unique_ptr:

#define CONCAT_(X, Y) X ## Y
#define CONCAT(X, Y) CONCAT_(X, Y)
#define MAKE_C() CONCAT(C, __LINE__)

#define MAKE_GENERIC_B(INSTANCE_NAME, IMPLEMENTATION) \
class MAKE_C() : public B                             \
{                                                     \
public:                                               \
    virtual void g() IMPLEMENTATION                   \
}; auto INSTANCE_NAME = std::make_unique<MAKE_C()>()

int main(int argc, char* argv[])
{
    MAKE_GENERIC_B(c,                             \
            {                                     \
                    char a[12] = { };             \
                    char d[10] = { };             \
                    memcpy(a, d, sizeof(d));      \
                    std::cout << x_ << std::endl; \
            }                                     \
    );
    c->g();

    MAKE_GENERIC_B(d, { std::cout << x_ * 2 << std::endl; });
    d->g();

    return 0;
}

As we cannot define classes in template arguments, we need to define it in advance. To get individual names, I use the __LINE__ macro, an alternative could be providing a class name as macro parameter.

Additionally, with the now named classes, you could provide a constructor, too, and even extend the macro to feed in parameters to it, if need be...

Upvotes: 2

Related Questions