https://www.kernel.org/doc/html/latest/filesystems/vfs.html
The vast majority of the function pointers in those structures are optional (even if not explicitly stated). To give a few common sense examples:
* If your filesystem does not support extended attributes, you would not implement .listxattr and instead set it to NULL.
* There are multiple ways of implementing read and write in file_operations. You have the basic read and write operations, and more efficient variants. You don’t need to implement the more efficient variants if you don’t want to implement them.
* The .bmap call is used to find out how the filesystem stores a file on a block device, which used to be used by the syslinux (and might still be). This obviously is incompatible with NFS (or any multidisk filesystem like ZFS) so it absolutely must be optional.
Then there are other options, like not supporting mmap, or not supporting creation/removal of subdirectories. That sounds absurd, but some FUSE filesystems, particularly those exporting a program’s statistics, don’t bother with either of those since they are not needed. I do not believe Linux sysfs allows users to make directories either.I could continue, but this gives a few examples of why you might want to have optional functionality in a class-like interface.
By the way, I mentioned setting things you do not implement to NULL. This is done simply by not specifying them when using the structure initializer syntax. The compiler will zero unspecified members.
If you want to make something fancy, templates, if constexpr requires func-to-call, call func.
You need to use hacks to shoehorn C++ class member functions into this. In particular, you need stub functions. Then either, call them and have them either return a special error code or throw an exception, or use a custom query function that is implemented by derived classes that lets you find out if a function is a stub or not to allow you to skip calling it. Another idea would be to use thread local storage with setjmp()/longjmp(), which is probably the sanest way of doing this insane idea:
https://godbolt.org/z/4GWdvsz6z
And the C way for comparison:
https://godbolt.org/z/qG3v5zcYc
The idea that the simplest way of approximating what you can do with function pointers in C structures via C++ class member functions is to use TLS and setjmp/longjmp shows what a bad idea it is to use class member functions instead of function pointers for optional functions in the first place.
The same C example compiled in C++23 mode, https://godbolt.org/z/MWa7qqrK7
As for possible alternatives, here is a basic one without taking into consideration virtual mechanics, only to show the principles.
#include <concepts>
template <class T>
concept has_mmap = requires (T obj)
{
{ obj.mmap() } -> std::convertible_to<int>;
};
class VFS {
public:
VFS() = default;
virtual ~VFS() = default;
};
class ExampleFS : public VFS {
// mmap not available
};
class ExampleWithMMAP : public VFS {
public:
int mmap() {
return 0;
}
};
int main() {
ExampleFS fs;
ExampleWithMMAP fsWithMMAP;
/*
<source>: In function 'int main()':
<source>:33:19: error: 'class ExampleFS' has no member named 'mmap'
40 | return fs.mmap();
|
*/
if constexpr (has_mmap<ExampleFS>) {
return fs.mmap();
}
// ExampleWithMMAP has mmap(), just call it without issues
if constexpr (has_mmap<ExampleWithMMAP>) {
return fsWithMMAP.mmap();
}
// want to use the variable name instead of the type?
if constexpr (has_mmap<decltype(fsWithMMAP)>) {
return fsWithMMAP.mmap();
}
}
-- https://godbolt.org/z/cjcbrzT3zNaturally it is possible to be a bit even more creative, and moreso with C++26 reflection.
The same C example compiled in C++23 mode, https://godbolt.org/z/MWa7qqrK7
Everyone knows this. The original comment was saying not to do this (even in C++) and use C++ classes instead. I was making the point that is a bad idea. You seem to have not understood that.