OSDev.org
https://forum.osdev.org/

C++ global single pass frustrations
https://forum.osdev.org/viewtopic.php?f=13&t=40993
Page 1 of 1

Author:  AndrewAPrice [ Tue Feb 23, 2021 10:29 am ]
Post subject:  C++ global single pass frustrations

This has annoyed me for a while. In C++ it's not possible to do this:

Code:
class Dog {
Cat TransformIntoACat();
};

class Cat {
Dog TransformIntoADog();
};


I can't declare "class Cat;" at the top, because TransformIntoACat() returns Cat as a value.

C++ doesn't have partial classes, so I can't add "TransformIntoACat" after both Cat and Dog is defined.

I could return a pointer, but I'd have to allocate memory for Cat/Dog.

As an alternative I could built a transformer functions:
Code:
Cat ConvertDogToCat(const Dog& dog);
Dog ConvertCatToDog(const Cat& cat);


Or, take a reference to Cat or Dog as a constructor arguments.

If C++ wasn't single pass (outside of classes/structs), it could see that Cat/Dog was defined later.

This is frustrating because I'm building an IDL->C++ code generator, and references to objects are passed around as {buffer, offset}, and types can reference each other in my IDL, e.g.: An Expression object can contain an Addition operator, which can contain 2 Expression operators, and in an ideal world, this would generate code such as:

Code:
class Expression {
public:
  AdditionOperation GetAdditionOperation();

private:
  Buffer* buffer;
  size_t offset;
};

class AdditionOperation {
public:
  Expression GetSideA();
  Expression GetSideB();

private:
  Buffer* buffer;
  size_t offset;
};


Because it would be nice to be able to do: expression.GetAdditionOperation().GetSideA();

But, instead I have to fake it with:
Code:
template <class T>
code Ref<T> {
public:
  T* operator::->() {
    return static_cast<T*>(this);
  }

private:
  Buffer* buffer;
  size_t offset;
}

class AdditionOperation;

class Expression {
public:
  Ref<AdditionOperation> GetAdditionOperation();

private:
  Buffer* buffer;
  size_t offset;
};

class AdditionOperation {
public:
  Ref<Expression> GetSideA();
  Ref<Expression> GetSideB();

private:
  Buffer* buffer;
  size_t offset;
};


Which at least gets me: expression->GetAdditionOperation()->GetSideA();

But, ducktyping Ref<T> into T seems "wrong" although it works.

I know C++ uses a multi-pass compiler because you don't need to use forward declarations inside of classes and structs, but it's frustrating that for declarations outside of a class it's acts like a single pass compiler.
[/rant]

Author:  iansjack [ Tue Feb 23, 2021 10:38 am ]
Post subject:  Re: C++ global single pass frustrations

Wouldn't a forward declaration work in the example you give?

Author:  Korona [ Tue Feb 23, 2021 10:39 am ]
Post subject:  Re: C++ global single pass frustrations

Huh? Why can't you just forward declare the structs in your first example?

Author:  Solar [ Tue Feb 23, 2021 10:47 am ]
Post subject:  Re: C++ global single pass frustrations

Hm. You address quite a few rather different issues in your post, which makes it difficult to address it in whole.

The Cat / Dog example is a bad one for the issues you bring up subsequently. It's simply not in the authority of the Dog class to transform to Cat. I'd probably aim for a Cat( Dog & ) conversion constructor.

Your IDL -> C++ example and what you actually want to do is a bit confused to me, I can't really make out what it is you want / need. But C++ is single pass, so wishing it weren't does not lead to results.

Generally speaking (and this isn't with regards to C++ specifically, but any language) as soon as you are at the point of "if only this language X were more like language Y", you're halfway down the wrong direction of a one-way road. Define clearly what it is you want to achieve in language X. Don't try to specify it in terms of language Y, because X isn't Y. Ask yourself (or someone familiar with X) how to best achieve what you want in X. Don't be halfway down the wrong road, because that adds several things to helping you -- telling you that you are going the wrong direction, why it is the wrong direction, and leading you back to the starting point.

Instead of just telling you the best way from your starting point to where you want to go. The "languages" variant of the XY problem, so to speak.

Plus, people might suspect you trying to bash a bit on the perceived shortcomings of language X, and we all know where that ends up. 8)

Author:  PeterX [ Tue Feb 23, 2021 12:15 pm ]
Post subject:  Re: C++ global single pass frustrations

Solar wrote:
Generally speaking (and this isn't with regards to C++ specifically, but any language) as soon as you are at the point of "if only this language X were more like language Y", you're halfway down the wrong direction of a one-way road. Define clearly what it is you want to achieve in language X. Don't try to specify it in terms of language Y, because X isn't Y. Ask yourself (or someone familiar with X) how to best achieve what you want in X. Don't be halfway down the wrong road, because that adds several things to helping you -- telling you that you are going the wrong direction, why it is the wrong direction, and leading you back to the starting point.

Instead of just telling you the best way from your starting point to where you want to go. The "languages" variant of the XY problem, so to speak.

Very true. I once tried to implement a loop in Scheme... (It's impossible, solution: tail recursion.)

I dare to add my two cent: I have the impression that this is one of the rare cases where someone is thinking too complicated. I'm quite sure that the real problem behind the cat dog issue can be solved with simple C++ techniques. I may be wrong, but I have the strong feeling that transformation is not needed. But prove me wrong... :)

Greetings
Peter

Author:  kzinti [ Tue Feb 23, 2021 12:33 pm ]
Post subject:  Re: C++ global single pass frustrations

You probably don't want to return Cats and Dogs by value, this would be inefficient for any non-trivial class.

This would be more idiomatic in C++:

Code:
class Dog
{
    Dog(const Cat& cat);
};

class Cat
{
    Cat(const Dog& cat);
};

And the implementation of the constructors go into .cpp files... No more circular dependency problems.

Author:  vvaltchev [ Tue Feb 23, 2021 1:46 pm ]
Post subject:  Re: C++ global single pass frustrations

AndrewAPrice wrote:
This has annoyed me for a while. In C++ it's not possible to do this:
Code:
class Dog {
Cat TransformIntoACat();
};

class Cat {
Dog TransformIntoADog();
};


I can't declare "class Cat;" at the top, because TransformIntoACat() returns Cat as a value.
Let's forget about design patterns etc. Actually, in C++ you can do that.
Check the example on compiler explorer: https://gcc.godbolt.org/z/KKfhTT
Code:
class Cat;

class Dog {
public:
    Cat TransformIntoACat();
};

class Cat {
public:
    Dog TransformIntoADog();
};

Cat Dog::TransformIntoACat() {
    return Cat();
}

Dog Cat::TransformIntoADog() {
    return Dog();
}

int main(int argc, char **argv) {
    return 0;
}

It compiles without any problems. Just, don't implement the methods in-line.

Vlad

Author:  Velko [ Wed Feb 24, 2021 3:19 am ]
Post subject:  Re: C++ global single pass frustrations

kzinti wrote:
You probably don't want to return Cats and Dogs by value, this would be inefficient for any non-trivial class.

That's what move constructors are for. Certain shortcuts for efficiency can be taken when it is known that the source will cease to exist after the call. For example, you can copy pointers to internally allocated memory to the new instance and set it to NULLs in the original.

The move semantics can also be used for conversion. For example:
Code:
class Cat
{
    Cat(Dog&& dog);
};

You ensure that you will not keep the original Dog. Note that it should take a non-const reference, as the source might need modifications to be "finalized" properly.

Author:  Gigasoft [ Wed Feb 24, 2021 3:20 am ]
Post subject:  Re: C++ global single pass frustrations

Sounds like a case of needing to get your money back from university.

Author:  Schol-R-LEA [ Wed Feb 24, 2021 9:26 am ]
Post subject:  Re: C++ global single pass frustrations

PeterX wrote:
Very true. I once tried to implement a loop in Scheme... (It's impossible, solution: tail recursion.)


Depends on what you mean by 'impossible'; one could always write a macro which transforms a loop idiom into a tail recursion, after all.

Indeed, there is a standard do loop syntax which works just this way... though it is insanely baroque and not at all recommended. I am not surprised if you never came across do because, first off most instructors never have either, and second off, the syntax for it is a mess. I am pretty sure that it was deliberately designed that way to discourage its use.

Also, while Scheme doesn't enforce functional programming, it does strongly encourage it, and conventional iteration is all about assignments and other side effects. Iteration syntax would just sort of go against the grain. Even 'iterations' in Scheme are done using tail recursion.

Even so, a macro for a more conventional-looking while statement isn't too difficult to write - if you've learned how to write Scheme macros, which most courses and even many textbooks simply never cover, because macrology is more than a bit hairy.

Code:
(define-syntax while
  ; a simple while loop
  (syntax-rules ()
    ((while condition expr1 expr2 ...)
     (let loop ()
       (if condition
           (begin expr1 expr2 ... (loop)))))))


Code:
> (define x 1)

> (while (< x 5)
    (display x)
    (newline)
    (set! x (+ 1 x)))

--> 1 2 3 4


Anyway, end of digression.

Author:  kzinti [ Wed Feb 24, 2021 11:41 am ]
Post subject:  Re: C++ global single pass frustrations

Velko wrote:
That's what move constructors are for. (..)You ensure that you will not keep the original Dog. Note that it should take a non-const reference, as the source might need modifications to be "finalized" properly.

I think you might be confusing things here. The original design wasn't destroying the Dog when creating the Cat. Move constructors don't help in the OP's scenario.

Also using a move constructor between two unrelated classes seems... unusual. How can you provide move semantics between two completely unrelated classes? Unless you know something about the internals of Cat and Dog, I am not sure this makes sense. Also you won't be able to access the private members without making the classes friends... Sounds like a slippery slope.

Author:  AndrewAPrice [ Thu Feb 25, 2021 4:41 pm ]
Post subject:  Re: C++ global single pass frustrations

vvaltchev wrote:
Let's forget about design patterns etc. Actually, in C++ you can do that.
Check the example on compiler explorer: https://gcc.godbolt.org/z/KKfhTT
Code:
class Cat;

class Dog {
public:
    Cat TransformIntoACat();
};

class Cat {
public:
    Dog TransformIntoADog();
};

Cat Dog::TransformIntoACat() {
    return Cat();
}

Dog Cat::TransformIntoADog() {
    return Dog();
}

int main(int argc, char **argv) {
    return 0;
}

It compiles without any problems. Just, don't implement the methods in-line.


You are right! Now I'm very embarrased.

I always assumed the compiler couldn't figure out the function signature without knowing the type sizes, but I was wrong (we only need to know the type sizes at calling and implementation time, not when declaring the function!) Thanks!

Author:  vvaltchev [ Fri Feb 26, 2021 5:28 am ]
Post subject:  Re: C++ global single pass frustrations

AndrewAPrice wrote:
You are right! Now I'm very embarrased.
There's nothing to be embarrassed about, man! Everybody can make a mistake ;-)
It happened to me, many times, as well. And yeah, C++ is not exactly a straightforward language. It's full of traps and pitfalls.

Author:  moonchild [ Fri Feb 26, 2021 4:02 pm ]
Post subject:  Re: C++ global single pass frustrations

PeterX wrote:
Very true. I once tried to implement a loop in Scheme... (It's impossible, solution: tail recursion.)


You can do loops in scheme. Many c-style loops can be done with do. And the common lisp loop macro, which is even more powerful, has been implemented in scheme (albeit in terms of unhygienic macros which, though widely supported, are not standard).

Page 1 of 1 All times are UTC - 6 hours
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/