Reputation: 73
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
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