2

EDIT: To be clear—right off the bat—this is a question about the linguistic abilities of a modern C++ compiler. Not a question about a specific goal. It's hard to describe such an abstract concept without clarifying this first and I've realized that some of the confusion revolves around what is commonly done rather than what can possibly be done. This is a very abstract question. Nothing here will compile and this is on purpose. Likewise, I'm not asking how to make this specific case work, but I'm asking if there's a way to get C++ to recognize what I would like to do (via templating or some kind of auto->decltype trick most likely if even possible).

I'm not exactly new to C++, but certainly not an expert. This is a fundamental problem that I've been struggling with since I've rediscovered the power of the language. The end goal here is to elegantly (and with as little code as possible) forward proper polymorphic return values based on calling context. For example...

class A {
    public:
        A& foo() {
            // do something mutant fooish
            return *this;
        };
};

class B: public A {
    public:
        B& bar() {
            // do something mutant barish
            return *this;
        };
};

int main(int argc, char** argv) {
    B yarp;
    yarp.foo().bar();
};

Compile error. Makes sense, C++ is designed to assume that you know nothing about what you're doing (which makes it highly optimizable but sometimes a pain... a high-level-mid-level OOP language).

Obvioiusly C++ compilers have gotten to the point where they're not only aware of what you are asking for (the A().foo() works and B().foo() works scenario), but also in what context your asking for it in (hence auto yarp = B() in C++11, the compiler knows that yarp is an instance of B). Is there a way to leverage this elegantly without having to reproduce a bunch of "using" statements or wrapped methods (which strangely don't get optimized out according to disassemble of gcc binaries)?

So is there a trick here? Something I simply haven't learned online. An auto -> decltype trick or a templating trick? Example:

class A {
    public:
        template <typename R>
        R& foo() {
            // do something fooish
            return (R&)*this;
        };
};

class B: public A {
    public:
        using A::foo<A>; // << even this would be better than nothing (but no where near  optimum)
        B& bar() {
            // do something barish
            return *this;
        };
};

Something even simpler? If you expand this concept to operators of a proxy template class meant for reference counting and gc deallocation, it becomes clear how problematic this becomes. Thanks in advance for any help (oh, and first post on stackoverflow, so if I got any formatting wrong or you have suggestions for a better structured post, apologies around and please point them out).

mr.stobbe
  • 612
  • 4
  • 19
  • 4
    "C++ is designed to assume that you know nothing about what you're doing", Actually, it's the other way around. It's designed assuming you *know* what you're doing. – R. Martinho Fernandes Oct 12 '11 at 05:25
  • 1
    You have to assume that everything also has to work if the definitions are in a different compilation unit (not available). So, there is no way of knowing what `foo` returns. Tomorrow a programmer might come and make it return sometimes `*this`, sometimes `static A a; return a;`. – UncleBens Oct 12 '11 at 06:38
  • @R. Martinho Fernandes - Poorly worded, agreed. What I was trying to say (poorly) was that C++ takes a very mid-level language approach to resolution. This means that it becomes blind unless you tell it explicitly what to do (in many cases, the obvious exception being template argument expansion). The result is you have to explicitly define the flow of actions/identities/etc rather than knowing that it will implicitly resolve/identify/etc correctly. Basically, it assumes that you can't keep track of implicit chain-of-events in your head (hence, it assumes you don't know what you're doing). – mr.stobbe Oct 12 '11 at 22:57
  • @R. Martinho Fernandes - Looked at the other way around, you're entirely right, it assumes that you know what you're doing because you're required to tell it what to do correctly. – mr.stobbe Oct 12 '11 at 22:59
  • @UncleBens - Good point. So that 100% rules out non-template solutions. Any template based ones? – mr.stobbe Oct 12 '11 at 23:01

3 Answers3

2

The obvious solution would be to just seperate it out into two lines:

yarp.foo();
yarp.bar();

or, alternatively, use static_cast's to get back a reference to B&, so

static_cast<B&>(yarp.foo()).bar();

Agreed, that's a little bit more verbose but chaining multiple member-function calls in a heirarchy in one line together like this is pretty unusual syntax for C++. It just doesn't come up a whole lot, so the language doesn't support that idiom terribly well. I have never come across a situation where I ran into this issue yet.

If you want to design some chainable functionality, there are other, better idioms you can use. One example is Boost's Range Adaptors that overload operator| to achieve chaining.

EDIT: Another option is to overload foo() in B&:

class B: public A {
    public:
        B& foo() { A::foo(); return *this; }

        B& bar() {
            // do something mutant barish
            return *this;
        };
};
Ayjay
  • 3,413
  • 15
  • 20
  • The obvious solution is not what I'm digging for here. Yes, that is obvious, but I'm looking for the compiler to forward-recognize what's going on. When a library becomes highly flexible and abstracted, the first obvious solution is not longer possible. The second scenario is problematic for two reasons... tons of copy-and-pasted code and gcc doesn't seem to optimize it out (I still don't understand why as it's simply a recast and not real code) which is bad because your creating needless stack frames on calls. – mr.stobbe Oct 12 '11 at 03:57
  • If what you are after is getting the pattern `yarp.foo().bar()` to work, my edit with the overloaded B::foo() will do exactly what you want with minimal overhead. If you want the compiler to be able to automatically determine that the A& returned by foo() is still a B& and thus supports bar(), that is beyond the capabilities of C++ compilers. It is trivial to add that capability though - just add a one-line overload into the class you want to support it, and you're done. – Ayjay Oct 12 '11 at 05:28
  • Again, yes, but it still is creating a needless stack frame for a call/return which is bad when you're optimizing for speed. Additionally (and just as importantly), if you're arbitrarily overloading every operator in a templated proxy class, that's A LOT of copy-and-pasted code floating around by the 3rd or forth child in the inheritance chain. – mr.stobbe Oct 12 '11 at 22:39
  • That call will be completely inlined out and optimised so there won't be any performance hit at all. You could use templates to move the proxy class' function definitions out the child class' definition if the clutter in the child class is an issue. – Ayjay Oct 13 '11 at 03:06
  • Hmmm... well, maybe I haven't disassembled test binaries in a bit but no, in gcc it does not appear to be optimized out with -O3. I'll double check maybe that's changed. Even if it has changed, if you read the full post you'll recognize that such an issue is only half the problem (elegance of complex code identity is the problem). – mr.stobbe Oct 13 '11 at 03:24
  • I mostly use VS2010 nowadays, and that call definitely gets optimised out. It doesn't sound right that GCC doesn't optimise that out - it is one of the simplest functions to inline! Also, I don't really see the solutions presented here as *that* inelegant and most of the ugliness is kept separate to the actual code, anyway. How often do you need to write this kind of code, anyway? Having to duplicate a few function signatures doesn't seem terribly onerous to me... – Ayjay Oct 13 '11 at 03:41
  • Agreed about the optimization thing as it's not real code. I may be wrong (again, haven't bothered to disassemble in a while) as it's not real code at all (simply a cast which is compile time). This means that it shouldn't even be inline. It should be non-existant. The real thing here is that it's the concept that matters, not the end result. If it were an everyday middle-of-the-rung project, why would I care? I'm asking about the linguistic abilities of C++. It would be a really really really nice thing to have a solution to. Simply for the sake of programming elegance. – mr.stobbe Oct 13 '11 at 04:37
  • Someone once told me that it's important to analyze things from extreme possibilities before you start narrowing in on realist results. This is a case in point. Imagine nearly infinite expansions of inheriting templated classes overriding nearly infinite methods that produce return values representing their base class? How many times would you have to copy and paste overriding code or how many times would you have to cast the end result? A major extreme I know, but that's the core question. – mr.stobbe Oct 13 '11 at 04:42
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/4214/discussion-between-ayjay-and-mr-stobbe) – Ayjay Oct 13 '11 at 05:24
2

I don't think there is a auto type detection since compiler even doesn't know what classes will inherit A.

And in your second trial, C++ forbid using a template specialization. So that won't compile.

I think there is another trick you could try is to make A a template

template <typename FinalType>
class A {
    public:
        FinalType& foo() {
            // do something fooish
            return static_cast<FinalType&>(*this);
        };
};

class B: public A<B> {
    public:
        B& bar() {
            // do something barish
            return *this;
        };
};
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
neuront
  • 9,312
  • 5
  • 42
  • 71
  • Just make A::foo a template function. – Ayjay Oct 12 '11 at 03:31
  • So template **should** expand automatically is what you're saying? I've tried that (many times) and it doesn't seem to work. At least for gcc 4.5+. That would be extremely nice if it did. – mr.stobbe Oct 12 '11 at 03:53
  • Oh man. I realize what's going on there. It's the same thing I've been doing with complex templates. Okay, Yes that works, but there are several problems with it. First, every "B" class must declare A. Not a huge deal, but still kind of violates an elegance clause. Where it gets tricky is when A is template (as you put it) and **that** is inherited by another template class. By-by auto-expansion. – mr.stobbe Oct 12 '11 at 04:20
  • Ohhh, and coming back to this and thinking about this more... the compiler should be perfectly aware of what's going on. Literally the only instance where the modern C++ compiler doesn't know your type (think about the whole auto declaration thing a bit) is when lvalue is auto and rvalue are "auto" (or both are templated). Maybe I'm just being too optimistic which might be the crux of the problem. – mr.stobbe Oct 12 '11 at 05:09
  • 1
    `class A { public: A& foo(); };` this is the crux of the problem. The compiler cannot guess you are returning `*this`. – R. Martinho Fernandes Oct 12 '11 at 05:38
  • @neuront: why did you use a reserved name? http://stackoverflow.com/q/228783/46642 – R. Martinho Fernandes Oct 12 '11 at 05:44
  • @R.MartinhoFernandes Sorry but I don't think that is reserved, and in STL names beginning with an underscore then camelcase are widely used for template parameter, like `std::vector ` when you read `man 3 std::vector`. – neuront Oct 12 '11 at 06:43
  • 1
    The compiler's implementation of the standard library is allowed to use reserved names (in fact, I believe it is required), so as to ensure user-defined code - e.g a macro - doesn't break standard library or the other way round. - The standard library implementation by no means can be considered a style guide. – UncleBens Oct 12 '11 at 06:47
  • @neuront: did you even read the link I posted? Names starting with an underscore followed by an uppercase letter are reserved and that's exactly why the standard library uses them. – R. Martinho Fernandes Oct 12 '11 at 07:09
  • @R.MartinhoFernandes Yes I did, but I couldn't find the reason that STL could use that kind of names while others are forbidden, since they are template parameter, not in the global scope. So I just follow STL as an example. – neuront Oct 12 '11 at 08:03
  • @neuront the reason is because the standard library *is part of the implementation of C++*. So, when it reads there that these names are "reserved to the implementation" it means you cannot use them unless you are the compiler or the standard library. – R. Martinho Fernandes Oct 12 '11 at 11:13
0

Erm you declare a instance of class B which has no method foo - so no wonder there is a compile error - did you mean yarp.bar().foo();

Adrian Cornish
  • 23,227
  • 13
  • 61
  • 77
  • Erm, no. The goal here is to get C++ to recognized that because in the current scope yarp is an instance of B, foo() returns B&. Did I not make that clear in the post? (EDIT): Or simpler yet, get C++ to factory via templating/auto/etc B& B::foo() – mr.stobbe Oct 12 '11 at 02:59
  • yarp.foo() returns an &A why would you expect it do otherwise. I have a feeling you are trying to do something else though that has a much simpler solution - do you have a simple real world example of what you are trying to accomplish – Adrian Cornish Oct 12 '11 at 03:42
  • Think a tad more abstact. Imagine this scenario carries through several levels of inheritance and templating. I could post a more "complete" specific example with a definitive (dummy) end goal, but my question is really actually about compiler linguistics. Abstact. Is there a way to forward factory the identity of B through A without explicitly defining it? – mr.stobbe Oct 12 '11 at 04:02