Thread overview
Taking a constant reference to a constant/non const object
Nov 15, 2017
helxi
Nov 15, 2017
Jonathan M Davis
Nov 15, 2017
helxi
Nov 15, 2017
helxi
Nov 15, 2017
Jonathan M Davis
November 15, 2017
Hi. What function signature should I use for receiving a constant reference of an r/l value object? Is it auto fn(inout ref const myClass obj)?
I want to:
1. Take a constant reference of the object, not copy them
2. The object itself may be const or non const.
November 15, 2017
On Wednesday, November 15, 2017 09:04:50 helxi via Digitalmars-d-learn wrote:
> Hi. What function signature should I use for receiving a constant
> reference of an r/l value object? Is it auto fn(inout ref const
> myClass obj)?
> I want to:
> 1. Take a constant reference of the object, not copy them
> 2. The object itself may be const or non const.

ref const(Type) would be the const version of ref Type. e.g.

auto foo(ref const(int) i) {...}

- Jonathan M Davis

November 15, 2017
On Wednesday, 15 November 2017 at 09:23:53 UTC, Jonathan M Davis wrote:
> On Wednesday, November 15, 2017 09:04:50 helxi via Digitalmars-d-learn wrote:
>> Hi. What function signature should I use for receiving a constant
>> reference of an r/l value object? Is it auto fn(inout ref const
>> myClass obj)?
>> I want to:
>> 1. Take a constant reference of the object, not copy them
>> 2. The object itself may be const or non const.
>
> ref const(Type) would be the const version of ref Type. e.g.
>
> auto foo(ref const(int) i) {...}
>
> - Jonathan M Davis

Thanks. Just a couple of follow-ups:
1. I've never seen a signature like `const(int)`is the enclosing parenthesis around the `int` necessary?
2. What effects does prefixing the arguments with `inout` have? For example:  fn(inout ref const string str){...}
November 15, 2017
On Wednesday, 15 November 2017 at 09:34:32 UTC, helxi wrote:
> On Wednesday, 15 November 2017 at 09:23:53 UTC, Jonathan M Davis wrote:
>> On Wednesday, November 15, 2017 09:04:50 helxi via Digitalmars-d-learn wrote:
>>> Hi. What function signature should I use for receiving a constant
>>> reference of an r/l value object? Is it auto fn(inout ref const
>>> myClass obj)?
>>> I want to:
>>> 1. Take a constant reference of the object, not copy them
>>> 2. The object itself may be const or non const.
>>
>> ref const(Type) would be the const version of ref Type. e.g.
>>
>> auto foo(ref const(int) i) {...}
>>
>> - Jonathan M Davis
>
> Thanks. Just a couple of follow-ups:
> 1. I've never seen a signature like `const(int)`is the enclosing parenthesis around the `int` necessary?
> 2. What effects does prefixing the arguments with `inout` have? For example:  fn(inout ref const string str){...}

Terribly sorry for my bad choice of words. Basically I want to utilize D's "inout" to avoid writing two functions like this:

#include <iostream>
#include <string>

void fn(std::string& str) {
  std::cout << str << " called from fn(std::string& str)"
            << "\n ";
}

void fn(const std::string& str) {
  std::cout << str << " alled from fn(const std::string& str)"
            << "\n";
}

int main() {
  fn("Test 1");
  std::string b = "test";
  b += " 2";
  fn(b);
}

November 15, 2017
On Wednesday, November 15, 2017 09:49:56 helxi via Digitalmars-d-learn wrote:
> On Wednesday, 15 November 2017 at 09:34:32 UTC, helxi wrote:
> > On Wednesday, 15 November 2017 at 09:23:53 UTC, Jonathan M
> >
> > Davis wrote:
> >> On Wednesday, November 15, 2017 09:04:50 helxi via
> >>
> >> Digitalmars-d-learn wrote:
> >>> Hi. What function signature should I use for receiving a
> >>> constant
> >>> reference of an r/l value object? Is it auto fn(inout ref
> >>> const
> >>> myClass obj)?
> >>> I want to:
> >>> 1. Take a constant reference of the object, not copy them
> >>> 2. The object itself may be const or non const.
> >>
> >> ref const(Type) would be the const version of ref Type. e.g.
> >>
> >> auto foo(ref const(int) i) {...}
> >>
> >> - Jonathan M Davis
> >
> > Thanks. Just a couple of follow-ups:
> > 1. I've never seen a signature like `const(int)`is the
> > enclosing parenthesis around the `int` necessary?

In this case, no. Without parens, the entire type is const; with parens, only the part in parens is const - e.g. const(int)* would be a pointer to a const int, whereas const int* or const(int*) would be a const pointer to a const int.

> > 2. What effects does prefixing the arguments with `inout` have? For example:  fn(inout ref const string str){...}

Combining inout and const is pretty meaningless. Arguably, it's stupid of the compiler to even allow it. inout makes it so that the constness of the return type is based on the constness of the parameter but treats the parameter as const within the function. But if you also mark the parameter with const, then it really is const.

> Terribly sorry for my bad choice of words. Basically I want to utilize D's "inout" to avoid writing two functions like this:
>
> #include <iostream>
> #include <string>
>
> void fn(std::string& str) {
>    std::cout << str << " called from fn(std::string& str)"
>              << "\n ";
> }
>
> void fn(const std::string& str) {
>    std::cout << str << " alled from fn(const std::string& str)"
>              << "\n";
> }
>
> int main() {
>    fn("Test 1");
>    std::string b = "test";
>    b += " 2";
>    fn(b);
> }

Well, why don't you just use const then? e.g.

void foo(ref const T T) {...}

will accept both const and non-const arguments. They just have to be lvalues, because the parameter is ref.

You could also templatize the function, e.g.

void foo(T)(ref T t) {...}

in which case you get different functions instantiated depending on the type or constness of the type.

The only real reason to use inout is if you need the return type is if you need the return type's constness to match the argument. So,

inout(SomeType) foo(ref inout Type t) {...}

works (the return type type doesn't need to match, but its constness will match the constness of the argument). But you need a return type for that to work, so your void function example couldn't be inout.

Now, if what you're actually look for is to have a function which accepts both lvalues and rvalues without copying lvalues and which works with const, then the thing to do would be

void foo(T)(auto ref T t) {...}

In that case, the constness will depend on the argument, and the refness will depend on the argument. e.g.

S s;
foo(s); // instantiation -> foo(ref S s)

const S s;
foo(s); // instantiation -> foo(ref const S s)

foo(S.init); // instantiation -> foo(S s)

foo(cast(const S)S.init); // instantiation -> foo(const S s)

So, lvalues will be passed by reference, whereas rvalues will be moved, and it doesn't matter whether the argument is const or not. Similarly, if a function returns auto ref, then not only is the type infered, but the refness is infered as well. However, to have any auto ref parameters, the function must be a template.

- Jonathan M Davis

November 15, 2017
On 11/15/17 6:46 AM, Jonathan M Davis wrote:
> On Wednesday, November 15, 2017 09:49:56 helxi via Digitalmars-d-learn
> wrote:
>> On Wednesday, 15 November 2017 at 09:34:32 UTC, helxi wrote:
>>> On Wednesday, 15 November 2017 at 09:23:53 UTC, Jonathan M
>>>
>>> Davis wrote:
>>>> On Wednesday, November 15, 2017 09:04:50 helxi via
>>>>
>>>> Digitalmars-d-learn wrote:
>>>>> Hi. What function signature should I use for receiving a
>>>>> constant
>>>>> reference of an r/l value object? Is it auto fn(inout ref
>>>>> const
>>>>> myClass obj)?
>>>>> I want to:
>>>>> 1. Take a constant reference of the object, not copy them
>>>>> 2. The object itself may be const or non const.
>>>>
>>>> ref const(Type) would be the const version of ref Type. e.g.
>>>>
>>>> auto foo(ref const(int) i) {...}
>>>>
>>>> - Jonathan M Davis
>>>
>>> Thanks. Just a couple of follow-ups:
>>> 1. I've never seen a signature like `const(int)`is the
>>> enclosing parenthesis around the `int` necessary?
> 
> In this case, no. Without parens, the entire type is const; with parens,
> only the part in parens is const - e.g. const(int)* would be a pointer to a
> const int, whereas const int* or const(int*) would be a const pointer to a
> const int.
> 
>>> 2. What effects does prefixing the arguments with `inout` have?
>>> For example:  fn(inout ref const string str){...}
> 
> Combining inout and const is pretty meaningless. Arguably, it's stupid of
> the compiler to even allow it. inout makes it so that the constness of the
> return type is based on the constness of the parameter but treats the
> parameter as const within the function. But if you also mark the parameter
> with const, then it really is const.
> 
>> Terribly sorry for my bad choice of words. Basically I want to
>> utilize D's "inout" to avoid writing two functions like this:
>>
>> #include <iostream>
>> #include <string>
>>
>> void fn(std::string& str) {
>>     std::cout << str << " called from fn(std::string& str)"
>>               << "\n ";
>> }
>>
>> void fn(const std::string& str) {
>>     std::cout << str << " alled from fn(const std::string& str)"
>>               << "\n";
>> }
>>
>> int main() {
>>     fn("Test 1");
>>     std::string b = "test";
>>     b += " 2";
>>     fn(b);
>> }

A few things here:

1. you do not need to take D strings by reference, they are simply a pointer and length. Unlike C++ where accepting a std::string by value will make a copy of the string, that does not happen in D.
2. inout doesn't work like a template. So it's not clear from your examples that even if you used inout you would get what you want.

So I would recommend this:

void fn(const(char)[] str) { ... }

Which will handle all cases, rvalues, lvalues, etc. And it won't be expensive, ever.

That is, unless you actually *want* to mutate lvalues :) Then you do have to write 2 functions, and I can't see how you avoid that.

> The only real reason to use inout is if you need the return type is if you
> need the return type's constness to match the argument. So,
> 
> inout(SomeType) foo(ref inout Type t) {...}
> 
> works (the return type type doesn't need to match, but its constness will
> match the constness of the argument). But you need a return type for that to
> work, so your void function example couldn't be inout.

This is not entirely true. There is one good reason to use inout on a parameter without having a return value, and that is the double-indirection problem.

For example:

void foo(ref const(int)* x)
void foo2(ref inout(int)* x)

int *p;
foo(p); // error.
foo2(p); // ok

-Steve