2

I am interested to know what the reason is for there to be no non-member virtual functions in C++. Especially considering the fact that it simply increases code layers when you want to achieve it, since you can define a virtual member-function and then call it from a non-member function.

EDIT: Just for reference, you can do that:

struct Base
{
    virtual void say() const
    {
        std::cout << "Base\n";
    }
};

struct Derived : public Base
{
    void say() const final
    {
        std::cout << "Derived\n";
    }
};

void say(Base* obj)
{
    obj->say();
}

say(static_cast<Base*>(new Derived()));

Edit 2: And there are indeed cases where you want virtual polymorphism, since you can have the case below which doesn't work in a similar fashion, since it prints Base whereas if you were to call it with the above code, in a similar fashion it will print Derived. I believe this summarizes the crux of the problem.

void say(Base* obj)
{
    std::cout << "Base\n";
}

void say(Derived* obj)
{
    std::cout << "Derived\n";
}
say(static_cast<Base*>(new Derived()));
lightxbulb
  • 1,251
  • 12
  • 29
  • 2
    The keyword `virtual` is tightly related to inheritance, objects and polymorphism. Non-member function have neither of those attributes. – Some programmer dude Apr 30 '19 at 07:29
  • 1
    what should be the effect of declaring a non-member function `virtual` ? – 463035818_is_not_an_ai Apr 30 '19 at 07:29
  • @user463035818 something like function overloading I guess, where you define it for some of the derived classes, and then it works even if you pass it as a base class. – lightxbulb Apr 30 '19 at 07:31
  • @user463035818 A use case might be multi-dimensional polymorphic dispatching, where you have something like "virtual" functions, e.g., for a combination of 2 polymorphic pointers. C++ doesn't support it (IIRC, some languages do). – Daniel Langr Apr 30 '19 at 07:33
  • That "duplicate" was more of a convenient reference; this question is more about why you can't have `static` virtual functions, and chance for a better answer than mine ought to be given. Therefore re-opened. – Bathsheba Apr 30 '19 at 07:37
  • The example code you show, what is the problem with that? What problem would virtual non-member functions solve in that snippet? I think it's very unclear what problem you're trying to solve. – Some programmer dude Apr 30 '19 at 07:38
  • @Someprogrammerdude It would just be syntactic sugar that saves you from redefining everything in the class, I simply found it curious that it's missing from the language, and wanted to know the reason. – lightxbulb Apr 30 '19 at 07:39
  • @lightxbulb Can you also add how that example would work if there were virtual non-member functions? – VLL Apr 30 '19 at 07:43
  • Can you please expand on the whole "saves you from redefining everything in the class" part? You only have to "redefine" function in a class if you actually need to override the functions from a parent class. If the parent class definition can be used there's no need to override ("redefine"). Do you have an actual use-case where "virtual" non-member functions would be useful? – Some programmer dude Apr 30 '19 at 07:46
  • @Ville-Valtteri Any concise and sane syntactic construction should work really, you can stick a virtual before one of the arguments or something I guess. – lightxbulb Apr 30 '19 at 07:47
  • @Someprogrammerdude I was trying to do some operator stuff with left addition when I stumbled into this. – lightxbulb Apr 30 '19 at 07:48
  • 3
    Perhaps it would help if you showed us what you have, what you're trying to do, ask about your *actual* problem instead of a vague solution (that doesn't exist)? – Some programmer dude Apr 30 '19 at 07:52
  • @Someprogrammerdude I did show you what I have, I believe it summarizes the problem well enough. – lightxbulb Apr 30 '19 at 08:16
  • So you have a pointer to the base class, and you want to call an overloaded non-member function for the derived class? That's just overloading and nothing about virtual function or polymorphism, and the solution is to cast the pointer you have to the type of the overloaded function you want to call. Either use overloading (with possible casting) or use polymorphism calling the virtual member function. And I don't see how overloaded (or "virtual") non-member function would solve the "redefining" problem. You need to "redefine" the non-member functions instead of member functions. – Some programmer dude Apr 30 '19 at 08:22
  • @Someprogrammerdude It can be really as simple as ```virtual void freeFunc(Derived* obj) {...}``` and ```virtual void freeFunc(Base* obj) {...}``` you obviously have to define them, since you want different behaviour, however, a) you are not sticking them inside the class where you possibly don't want them, b) you don't have to call stuff like obj->... Neither of those is tragic as already mentioned - it's just syntactic sugar. Also for functions with more arguments, the virtual may actually be appended to the arguments. – lightxbulb Apr 30 '19 at 08:35

3 Answers3

4

A non-member function does not require an implicit this pointer in order to invoke it.

But virtual functions require a this pointer (i.e. an object instance) in order for polymorphism to work.

And there's the contradiction: so it's not possible to have a polymorphic non-member function.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • How does this tie into implementation and the vtable, it's certainly feasible to implement it, isn't it? – lightxbulb Apr 30 '19 at 07:30
  • @lightxbulb: No I don't believe it is feasible. Spend some time thinking it through (and perhaps answer the question yourself). I avoided mentioning a *v-table* as that's not a C++ language concept, rather an implementation concept. – Bathsheba Apr 30 '19 at 07:31
  • It's certainly feasible considering the workaround: just defining the virtual methods and then calling them from inside a non-member functions, it's just syntactic sugar that would save code duplication and verbosity. – lightxbulb Apr 30 '19 at 07:32
  • 1
    @lightxbulb: Go on, answer based on that. And see how the community reacts. I still thing it contradicts. – Bathsheba Apr 30 '19 at 07:34
  • How would you call them from non-member function? Where would you get a pointer to the class instance? And what exactly would non-member function virtuality mean when there is no hierarchy in non-member functions (as opposed to member functions). – Jeysym Apr 30 '19 at 07:35
  • @Jeysym see the edit. It can be just syntactic sugar, rather than piling up on layers of stuff. – lightxbulb Apr 30 '19 at 07:37
  • @lightxbulb: One thing, what would happen if an object was not default-constructable? – Bathsheba Apr 30 '19 at 07:41
  • @Bathsheba Are you talking about this: ```say(new Derived())``` ? – lightxbulb Apr 30 '19 at 07:52
  • @lightxbulb: Yup. – Bathsheba Apr 30 '19 at 07:55
  • @Bathsheba Just call say(new Derived(arguments))? – lightxbulb Apr 30 '19 at 08:01
  • @lightxbulb: Then how would that be specified from a language perspective? And then there's destruction to think about. – Bathsheba Apr 30 '19 at 08:01
  • @Bathsheba What does this have to do with the problem I am asking about? Most of the comments/answers are really debating whether this should be implemented, and not why it is hard to implement/hasn't been implemented. I expected something similar to J. Antonio Perez's answer tbh. – lightxbulb Apr 30 '19 at 08:05
  • @lightxbulb: I don't think we understand each other. It's not implemented because it's impossible to implement. Although of course you can come up with some code that will broadly implement what you want but that's different to having this in the language itself. I've challenged you to come up with the counter-claim, but you haven't. The question you ask is a good one by the way. But I think we should end the discussion now. – Bathsheba Apr 30 '19 at 08:07
  • @Bathsheba "It's not implemented because it's impossible to implement." - that would be incorrect, see J. Antonio Perez's answer. But I agree, let's leave it at that. We're obviously talking past each other. – lightxbulb Apr 30 '19 at 08:17
2

Having virtual non-member functions is technically challenging to compile.

Virtual functions are usually implemented with a vtable. Classes with virtual member functions store a pointer to that vtable, and that vtable has all the requisite functions added to it. When a virtual function is invoked, the exact function to invoke is looked up in the vtable.

Consider this: I'm writing a library in C++. For user convenience, and to reduce compiletimes, the library is distributed as:

  • the header files for the library
  • binary files that provide the implementation of the functions defined in the headers.

So what's the problem?

These binary files will also contain the vtables for any classes with virtual functions within the header files. In order to add virtual functions to a base class, the compiler will have to read and process the binary representation of the library files, modifying the vtables to add the necessary functions.

This would greatly increase the complexity of linking (making the compiler partially responsible for doing so), and it would bloat executable size (any dynamically loaded libraries would have to be statically linked, since the compiler might not have permission to modify their contents).

Are there technical work-arounds?

Yes, although it would require the implementation of the class to be present in the header file, like a template. Alternatively, the new module system could provide a way to implement this feature by forgoing the need to have separate implementation files.

Even then, it would require a lot of work on the part of compiler developers, and there has not been much demand for this feature. The main benefit this feature provides is being able to quickly and easily overload functions for specific derived classes, which itself is considered something of a code smell (since you'd come close to breaking encapsulation - a library writer writing a function that returns a pointer to a base class may want to change which derived class it returns, for example).

Alecto Irene Perez
  • 10,321
  • 23
  • 46
  • I believe that this is the closest to what I had in mind for answer - it's actually discussing the details as to why this is not implemented. Thanks a lot, if there's no better answer in a few days, I will accept this as the answer. – lightxbulb Apr 30 '19 at 08:04
  • 1
    JSYK, anyone can write proposals and submit them to the C++ standards committee. If you show up at one of the meetings, you can even vote for or against proposals in plenary votes (even if you're not part of the committee). There's also a google group to suggest new ideas to the C++ standards committee (although you should have a lot of research ready to support your position) – Alecto Irene Perez Apr 30 '19 at 08:07
  • 1
    Proposals are really, seriously considered. C++ has grown a lot since the old days, and it's because of community contributions. C++11 was a huge release (it's what introduced move semantics, variadic templates, lambdas, and a ton of other stuff). There was another release in 2014 and again in 2017, and the C++20 release is absolutely massive with tons of new features. C++20 is in it's final stages right now, but the next release after C++20 is gonna be in 2023, and there's plenty of time to submit a proposal for that one – Alecto Irene Perez Apr 30 '19 at 08:10
  • 1
    I really encourage community involvement, and it's one of the things I like so much about C++. Here's a link that outlines how to submit a proposal: https://isocpp.org/std/submit-a-proposal – Alecto Irene Perez Apr 30 '19 at 08:11
  • I know that a lot has changed (improved?) in C++ and appreciate that (that's why I am programming in C++), but the fact remains that a multitude of "obvious" features are simply not there, like partial function template specialization, or virtual template functions, or the nameless struct union that's supported by major compilers but is not standard conformant etc.. And it is not like those questions have not been discussed on stackoverflow and elsewhere before. Maybe a proposal will indeed solve those problems, I do not believe I am qualified or can do it justice though if I write it. – lightxbulb Apr 30 '19 at 08:15
  • Wait, so what stops the compiler from simply using the workaround outlined above, it will be as simple as creating a virtual method in the class, and calling that from the free function, wouldn't it? So almost a (pre-)compile time substitution problem. – lightxbulb Apr 30 '19 at 08:39
  • 1
    That would only work for classes where the compiler had access to the source of the class (so it wouldn't work for statically or dynamically linked libraries). With the module system coming out in C++20, that won't be as much of a problem, but it'd have to be standardized as part of the language for there to be widespread compiler support – Alecto Irene Perez Apr 30 '19 at 08:46
1

When you want to use polymorphism in free functions you basically have two options. Either you overload the function or you call virtual functions:

#include <iostream>

struct base {
    virtual void func() = 0;
};

struct foo : base { void func() { std::cout << "foo\n"; } };
struct bar : base { void func() { std::cout << "bar\n"; } };

void f(foo& f) { f.func(); }
void f(bar& f) { f.func(); }

void g(base& b) { b.func(); }

int main() {
    foo a;
    bar b;
    f(a);
    f(b);
    g(a);
    g(b);
}

Considering that the main difference to member functions is the implicit this parameter, g is actually rather close to what I'd call a "virtual free function". However, other than that there are no virtual non-member functions in C++.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • I do get that there are no virtual free functions, I want to know the reason why this is not in the language. Is there a valid reason for this? Or is it just an oversight? – lightxbulb Apr 30 '19 at 07:42
  • @lightxbulb what could you do with such virtual free functions that you cannot do already now? What would it mean for a free function to be virtual? – 463035818_is_not_an_ai Apr 30 '19 at 07:43
  • @lightxbulb in some sense you get the "virtual" for free with free functions if you just pass a pointer/reference to a base class. there is no need to declare them virtual – 463035818_is_not_an_ai Apr 30 '19 at 07:44
  • As already mentioned - it's just syntactic sugar that will save you typing, layers, and code duplication. Nothing too critical, but it is certainly curious for me that this is not supported. – lightxbulb Apr 30 '19 at 07:44
  • @lightxbulb what typing would it save? syntactic sugar for what? As i said the "virtual" comes for free on free functions, you dont have to declare it as virtual, the only thing you have to do is to put a `&` next to the parameter. How do you want this any shorter? ;) – 463035818_is_not_an_ai Apr 30 '19 at 07:46
  • @lightxbuld consider `h(base b) {}` if you want you can call this "non-virtual" while `g(base& b) {}` does make use of polymorphism. Maybe you can demonstrate with some code what you want to achieve? In your current example I dont see what typing you want to save, you didnt have to put anything extra to make `say` call the virtual member method – 463035818_is_not_an_ai Apr 30 '19 at 07:49
  • Maybe you are right and it will actually be solved by this, I guess my issue was that doesn't exactly work with operator overloading but that's a different matter I guess. – lightxbulb Apr 30 '19 at 07:50
  • You can keep the definitions outside of the class, without indirectly calling them was my point, I'll try to look into your other statement though, and see if it applies to my use case. Thanks. – lightxbulb Apr 30 '19 at 07:51
  • @lightxbulb then it seems like we have a [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). The actual problem you have (something with operator overloading) is not apparent from your question – 463035818_is_not_an_ai Apr 30 '19 at 07:51
  • Yes I know, that's why I said it's a different matter. The current question concerns only what I have asked here, and I think your comments answer it, I'll check whether everything works correctly, and I'll accept it afterwards. – lightxbulb Apr 30 '19 at 07:53
  • @lightxbulb dont consider comments as part of the answer. Maybe i'll refactor them later into the answer, but until then I dont think my answer is the best available ;) – 463035818_is_not_an_ai Apr 30 '19 at 07:55
  • @lightxbulb i dont understand what you call "the crux of the problem". In your example you deliberately cast the derived to a base pointer, so you just get what you ask for. Dont cast and it works. Really no offense, but I have the feeling you are trying to make up a problem where there is none. Instead of modifying this question you should ask a new one that asks about your actual problem instead of some abstraction of your problem that is so much abstracted that the problem is not present anymore – 463035818_is_not_an_ai Apr 30 '19 at 09:05
  • I deliberately cast it to ```Base*``` to emphasize what I am trying to do, since I considered it was not explicit enough just from writing out - you actually confused me with the static overloading argument, that was not what I was trying to do, so I clarified it. Basically I illustrated where the virtualness comes into play, which I believe you realized considering your example code above which calls a virtual function. – lightxbulb Apr 30 '19 at 09:23