August 05, 2012

Leng: return types

I've been thinking a lot about return values in C++ and how I'd like it to work in Leng. It's a hard because the variable that is going to receive the value needs to be of the correct type. In C++ we have:
 T Foo1(); //(1) copy
 T& Foo2(); //(2) reference
 const T& Foo3(); //(3) const reference
You could also return a const value but that doesn't really make sense in the common case and I've yet to come across where it was needed and no more elegant alternatives were available.

I've previously discussed how I planned to deal with arguments and I'm wondering if there is an easy way to use the same system. After a while I've got an idea which needs some polish but that I think could make the system more consistent. Here it goes:

When you define a type T you are actually defining two types Ref<T> and Val<T> and one smart alias T. The compiler will automatically decide what real type the alias T actually refers to in each context.

Also, following the discussion about constness on arguments, I've decided to omit completely the const word and use & (ampersand) as the symbol for non-const.
For example:
 // variable in the current scope (temporary, local or argument)
 // translates to C++: T
 T& Foo1() { return T(); }
 // variable from outer scope (member or global)
 // translates to C++: T&
 T& Foo2() { return (*this); }
 // variable from outer scope (member or global)
 // translates to C++: const T&
 T Foo3() { return (*this); }
This has several problems. The most obvious:
- The compiler is doing more stuff behind your back which you need to be aware of for rare situations.
- Breaks the compile model for C++ because suddenly the signature of a function depends on the declaration which means that even if I define the methods in a cpp file, if I change the variable being returned, the client code may need to be recompiled (or an error for invalid return type triggered). This means unexpectedly longer compile times and more difficulties when making changes to library code that has already been distributed. Although this is a problem in C++ too (if you change the returned variable you'll probably  have to change your return type from T to const T& anyway), it's less obvious when making changes in leng. Compile times are my biggest concern here as I believe they really affect programmer productivity in when working with big code bases.

The benefits:
- Most obviously, makes declarations more simple and readable.
- You can still use the unaliased types, which means you can have a tighter control on what the compiler is going to end up generating, if you need it:
Ref<T> Foo3() // translates to C++: const T& Foo3()
{
 // return (*this);
 return T(); // compiler error : returning address of temporary variable.
}
Of course, this needs all the conversion rules between the Ref and Val types, which are pretty much the same as exist in C++ for conversion between variables and references to variables:

leng C++
Ref<T> const T&
Ref<T&>  T&
Val<T>  const T
Val<T&>  T

Compiler translations will be deduced contextually:

alias translation context
T Val<T> return current scope variable (temporary, local or Val argument
Ref<T> return outer scope variable (member, global or Ref argument)
T Val<T> member declaration
T Val<T> local declaration
T Val<T> argument declaration where T is a fundamental type
T Ref<T> argument declaration where T is a user defined type

And equivalent for T& with Val<T&> and Ref<T&>

What a fundamental type is, however, is still to be defined.

There is also another missing point: in C++11 we now get rvalue references and move semantics. I still need to think on how to include them in this variable model but it's something that definitely needs to be there.

No comments:

Post a Comment