3

I've made the argument to a function const& because I want to be able to pass "string literals" to it. However I'm wondering whether it's possible to modify the reference, something like this:

void addObjectToDictionary(int* obj, const std::string& name)
{
     for (int = 0; i < 10; ++i)
     {
           if (/*Name already exists in dictionary*/)
                name = "object" + std::to_string(rand());
           else { /*Add obj to dictionary*/; return; }
     }
}

int main()
{
     addObjectToDictionary(new int(6), "somename");
)

I'm wondering whether casting away the const on the std::string reference is undefined. Another answer on this site says:

const_cast is safe only if you're casting a variable that was originally non-const. For example, if you have a function that takes a parameter of a const char *, and you pass in a modifiable char *, it's safe to const_cast that parameter back to a char * and modify it.

I'd just like to know, however, whether the temporary std::string object created when calling the function is considered const or not.

Zebrafish
  • 11,682
  • 3
  • 43
  • 119
  • No, temporaries are not const. If you really want to accept temporaries, then look into using rvalue-references instead. – Remy Lebeau Mar 27 '20 at 20:03
  • 4
    Your confusion might come from that non-constant references can't be bound to temporaries. Only constant references can be used for temporaries. – Some programmer dude Mar 27 '20 at 20:05
  • @Someprogrammerdude Because the language requires you to take a temporary argument as const referenceI thought there was an expectation that you wouldn't modify the temporary, and so it would be const. So it can const_cast away then instead of creating a new string? – Zebrafish Mar 27 '20 at 20:07
  • From the caling function's point of view, how would that change be reflected there? – R Sahu Mar 27 '20 at 20:11
  • Actually an `rvalue` reference (`T&&`) binds to temporaries, and is preferred over `const T&` in overload resolution. So your premise that temporaries must be taken by `const T&` is incorrect. – Zuodian Hu Mar 27 '20 at 20:11
  • 2
    You can also just take temporaries by non-const value, which is almost certainly what you want to do here – Mooing Duck Mar 27 '20 at 20:36
  • The reason the language doesn't allow temporaries as an argument to a non-const reference parameter is because the non-const reference implies that the variable is an _update_ or _out_ variable. Since it will be modified, there are very few use-cases for a temporary. And the few use-case counterarguments (e.g., unused dummy variable) weren't as compelling as the accidental temporary case, especially in light of implicit conversions (which make a temporary). – Eljay Mar 27 '20 at 20:42
  • 1
    Possible duplicate: [Am I right in saying that const_cast followed by modification on a ref-to-const bound to a temporary is okay](https://stackoverflow.com/questions/8245027/am-i-right-in-saying-that-const-cast-followed-by-modification-on-a-ref-to-const) – rustyx Mar 27 '20 at 20:55

2 Answers2

4

First of all, you can use const_cast whenever you want. It's just that if you attempt to modify an object that is actually const, in a way the compiler can't catch at compile time, the behaviour is undefined. Using const_cast merely increases the risk that you are going to do this.

As for your question of whether the temporary is const in your case: the answer appears to be "yes". There have been numerous defect reports regarding the wording of reference initialization against each of the versions of the C++ standard, so I will just discuss the C++17 wording here. The relevant provision in the standard is [dcl.init.ref]/5.2.2.1:

If T1 or T2 is a class type and T1 is not reference-related to T2, user-defined conversions are considered using the rules for copy-initialization of an object of type “cv1 T1” by user-defined conversion (11.6, 16.3.1.4, 16.3.1.5); the program is ill-formed if the corresponding non-reference copy-initialization would be ill-formed. The result of the call to the conversion function, as described for the non-reference copy-initialization, is then used to direct-initialize the reference. For this direct-initialization, user-defined conversions are not considered.

This isn't super clear about whether the std::string prvalue is cv-qualified, but in any case the prvalue will then be used to initialize the reference, which is governed by p5.2.1, which requires that the prvalue inherit the cv-qualifiers of the reference being initialized. So it is clear that the temporary object that is created will have type const std::string.

So, in:

const std::string& name = "somename";

you get a const temporary, but when you do

std::string&& name = "somename";

you get a non-const temporary. The cv-qualification of the temporary matches that of the reference.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • Is your conclusion "If you do `const std::string& name = "somename"; const_cast(name).clear()` this is undefined, but if you do `std::string&& name = "somename"; name.clear()`, this is well defined."? – Aykhan Hagverdili Mar 27 '20 at 20:48
  • 1
    @Ayxan Yes, the former is undefined because the actual temporary object that is created is `const`, but the latter is fine, you are just modifying a temporary, which is odd. – Brian Bi Mar 27 '20 at 20:49
  • Is [this](https://gcc.godbolt.org/z/Sivp8w) undefined? Is the temporary const? If so, how did `A{}.set(42);` succeed? If not, why is it undefined? – Aykhan Hagverdili Mar 27 '20 at 20:59
  • @Ayxan In that case the temporary is not const, because you directly created it with a non-const type, then the reference binding simply bound to that existing object. – Brian Bi Mar 27 '20 at 21:00
  • What does mean to create it with a non-const type? Is it const only if the object is created with some implicit conversion? – Aykhan Hagverdili Mar 27 '20 at 21:05
  • @Ayxan When you use the syntax `A{}`, you get an object of type `A`. – Brian Bi Mar 27 '20 at 21:06
3

I've made the argument to a function const& because I want to be able to pass "string literals" to it.

If you're interested, the exact type of a string literal like this...

"string literal"

... is const char[15]. It just so happens that a std::string has a constructor that takes const char*, which const char[15] automatically decays to, so a string literal binds to a const std::string& parameter.

(const char[15]) --> decay --> (const char*) --> bind --> std::string::string( const char* )

This creates a temporary std::string, which holds a copy of the string literal. Your function then takes that temporary std::string as a const std::string&. The original temporary is not actually const Whether or not the original temporary is const is wishy-washy in the standard apparently, but it is const in C++17, according to the other answer.

However I'm wondering whether it's possible to modify the reference

I'm wondering whether casting away the const on the std::string reference is undefined.

If you want to modify the temporary, there's no need for const_cast. The language gives you a way to bind to temporaries in a non-const way: the rvalue reference.

// this is a const lvalue reference
// it binds to temporaries
void addObjectToDictionary(int* obj, const std::string& name);

// this is an rvalue reference
// it also binds to temporaries, and will bind to temporaries instead of a
// const lvalue reference if both overloads exist
void addObjectToDictionary(int* obj, std::string&& name);

The answer to your stated question...

I'd just like to know, however, whether the temporary std::string object created when calling the function is considered const or not.

... is no, temporaries are not const apparently yes for C++17 according to the other answer. However, y You should also not take a temporary by const lvalue reference and cast the const away, because that signature also binds to actually const objects. Instead, you can bind to a temporary in a non-const way using an rvalue reference parameter.

As a bonus, if you want to bind directly to a string literal, you can do this:

// this templates functions that bind to the exact type of any string literal
template<std::size_t N>
void addObjectToDictionary(int* obj, const char ( &name )[N] );

This template generates functions that bind to the exact types of string literals of any length. This may be a little overboard.

EDIT

There's a suggestion in the comments to take the string by value (std::string, no reference). This is also a perfectly valid way to "sink" a string into your function.

// this will bind to string literals without creating a temporary
void addObjectToDictionary(int* obj, std::string name);

What happens here, then, is a bit different. When passing a string literal to a const std::string& parameter, you get a const reference to a temporary std::string. When passing a string literal to a plain old std::string value, what used to be a temporary is now your very own std::string value object to do with as you wish, constructed from the string literal.

Zuodian Hu
  • 979
  • 4
  • 9