14

In the following line of code:

bootrec_reset(File(path, size, off), blksize);

Calling a function with prototype:

static void bootrec_reset(File &file, ssize_t blksize);

I receive this error:

libcpfs/mkfs.cc:99:53: error: invalid initialization of non-const reference of type 'File&' from an rvalue of type 'File'

libcpfs/mkfs.cc:30:13: error: in passing argument 1 of 'void bootrec_reset(File&, ssize_t)'

I'm aware that you can not pass non-const references (const &) to rvalues according to the standard. MSVC however allows you to do this (see this question). This question attempts to explain why but the answer makes no sense as he is using references to literals, which are a corner case and should obviously be disallowed.

In the given example it's clear to see that following order of events will occur (as it does in MSVC):

  1. File's constructor will be called.
  2. A reference to the File, and blksize, are pushed on the stack.
  3. bootrec_reset makes use of file.
  4. After returning from bootrec_reset, the temporary File is destroyed.

It's necessary to point out that the File reference needs to be non-const, as it's a temporary handle to a file, on which non-const methods are invoked. Furthermore I don't want to pass the File's constructor arguments to bootrec_reset to be constructed there, nor do I see any reason to manually construct and destroy a File object in the caller.

So my questions are:

  1. What justifies the C++ standard disallowing non-const references in this manner?
  2. How can I force GCC to permit this code?
  3. Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?
Community
  • 1
  • 1
Matt Joiner
  • 112,946
  • 110
  • 377
  • 526

7 Answers7

12

Yes, the fact that plain functions cannot bind non-const references to temporaries -- but methods can -- has always bugged me. TTBOMK the rationale goes something like this (sourced from this comp.lang.c++.moderated thread):

Suppose you have:

 void inc( long &x ) { ++x; }

 void test() {
     int y = 0;
     inc( y );
     std::cout << y;
 } 

If you allowed the long &x parameter of inc() to bind to a temporary long copy made from y, this code obviously wouldn't do what you expect -- the compiler would just silently produce code that leaves y unchanged. Apparently this was a common source of bugs in the early C++ days.

Had I designed C++, my preference would have been to allow non-const references to bind to temporaries, but to forbid automatic conversions from lvalues to temporaries when binding to references. But who knows, that might well have opened up a different can of worms...

j_random_hacker
  • 50,331
  • 10
  • 105
  • 169
  • I wasn't aware that lvalues were converted to temporaries when binding to references. If so the decision makes sense. *edit*: I've just tested it and they're not? – Matt Joiner Nov 03 '10 at 07:10
  • @Matt: the rules changed with C++0x. That rule change affects e.g. passing `std::auto_ptr` as argument. Anyway, when binding to a reference to const, and the actual argument isn't of the required type (or a derived class), then a temporary must necessarily be introduced. Cheers, – Cheers and hth. - Alf Nov 03 '10 at 07:19
  • @Matt: What did you test exactly? My snippet should not compile because C++ explicitly disallows binding a non-const ref to a temporary (the latest MSVC++ and g++ observe this); changing the ref to a const ref will of course fail compilation due to `++x;`. – j_random_hacker Nov 03 '10 at 07:23
  • `void blah(int &);` then in caller: `long a = 1; blah(a);` gives an error on gcc 4.4.3 (so i should hope, but you indicate otherwise). – Matt Joiner Nov 03 '10 at 10:17
  • I should add that I agree, I'd want it the same way you've indicated in your final paragraph. – Matt Joiner Nov 03 '10 at 10:19
  • @Matt: I don't indicate otherwise -- I illustrate why that very rule is in place with an example showing the horribleness that would ensue if, hypothetically, it was not there. (Note the "If" in my answer.) – j_random_hacker Nov 04 '10 at 00:49
  • Downvoter, could you explain why? Otherwise I don't know. Thanks. – j_random_hacker Nov 05 '10 at 06:36
  • Sure. I downvoted you because you left a comment on my answer saying that my example was irrelevant, then after that, made an answer of your own using the same example. By your own reasoning then, your answer is irrelevant. – Ben Voigt Nov 05 '10 at 17:48
  • @Ben Voigt: It's true that my example makes the same point as your latest version does, so by all means downvote if you feel I'm plagiarising. But my comment that your answer was irrelevant referred to the first version of your code snippet -- which had a signature of `void func(const int&)` and thus was in fact irrelevant. – j_random_hacker Nov 06 '10 at 09:53
7
  • "What justifies the C++ standard disallowing non-const references in this manner?"

Practical experience with the opposite convention, which was how things worked originally. C++ is to a large degree an evolved language, not a designed one. Largely, the rules that are still there are those that turned out to work (although some BIG exceptions to that occurred with the 1998 standardization, e.g. the infamous export, where the committee invented rather than standardizing existing practice).

For the binding rule one had not only the experience in C++, but also similar experience with other languages such as Fortran.

As @j_random_hacker notes in his answer (which as I wrote this was scored 0, showing that the scoring in SO really doesn't work as a measure of quality), the most serious problems have to do with implicit conversions and overload resolution.

  • "How can I force GCC to permit this code?"

You can't.

Instead of ...

bootrec_reset(File(path, size, off), blksize);

... write ...

File f(path, size, off);
bootrec_reset(f, blksize);

Or define an appropriate overload of bootrec_reset. Or, if "clever" code appeals, you can in principle write bootrec_reset(tempref(File(path, size, off)), blksize);, where you simply define tempref to return its argument reference appropriately const-casted. But even though that's a technical solution, don't.

  • "Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?"

Nope, nothing that changes things for the given code.

If you're willing to rewrite, however, then you can use e.g. C++0x rvalue references, or the C++98 workarounds shown above.

Cheers & hth.,

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
6

Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?

Yes. Since every name is an lvalue, it is almost trivial to treat any expression as if it was an lvalue:

template <typename T>
T& as_lvalue(T&& x)
{
    return x;
}

// ...

bootrec_reset(as_lvalue(File(path, size, off)), blksize);
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
2
  1. Is a fairly arbitrary decision - non-const references to temporaries are allowed when the temporary is the subject of a method call, for example (e.g. the "swap trick" to free the memory allocated by a vector, std::vector<type>().swap(some_vector);)
  2. Short of giving the temporary a name, I don't think you can.
  3. As far as I'm aware this rule exists in C++0x too (for regular references), but rvalue references specifically exist so you can bind references to temporaries - so changing bootrec_reset to take a File && should make the code legal.
Fabian Giesen
  • 3,231
  • 16
  • 16
  • The comparison to the member-access case is not really that good. The member-access operator doesn't consider conversions. Binding of const references does. – Ben Voigt Nov 03 '10 at 04:30
  • 1
    That's an orthogonal issue. It's the combination of both (binding to temporaries that were obtained from an implicit conversion) that really causes unexpected/surprising behavior; you could conceivably allow references to temporaries but not allow any implicit conversions to take place in that context. – Fabian Giesen Nov 03 '10 at 04:44
  • But implicit conversions ARE allowed when binding non-const references. You're not seriously suggesting taking that away, are you? Then we're back to having things that bind to const references and not to non-const references, which is where the language is right now. – Ben Voigt Nov 03 '10 at 04:50
  • 1
    @Ben Voigt: Like Fabian I don't understand why taking away implicit conversions when binding to references is a bad idea -- can you elaborate? It seems to me like the perfect solution. In those cases where you deliberately want to create a temporary rvalue and bind it, you can use `template U make(T t) { return t; }` and write e.g. `int &i_know_i_want_a_copy = make(42.69);`. (I.e. we would still allow binding to *existing* temporaries of the correct type, with magical lifetime extension as we currently have for const refs to temporaries.) – j_random_hacker Nov 03 '10 at 06:05
  • 1
    @Ben Voigt: Actually just `int &i_know_i_want_a_copy = int(42.69);` would have sufficed in my previous comment... In hindsight didn't need that fancy function template :) – j_random_hacker Nov 03 '10 at 07:30
1

Alternatively, simply overload.

static void bootrec_reset(File &&file, ssize_t blksize) {
    return bootrec_reset(file, blksize);
}

This is the easiest solution.

Puppy
  • 144,682
  • 38
  • 256
  • 465
1

Please note that calling C++0x "jibberish" is not presenting a very favorable picture of your coding ability or desire to understand the language.

1) Is actually not so arbitrary. Allowing non-const references to bind to r-values leads to extremely confusing code. I recently filed a bug against MSVC which relates to this, where the non-standard behavior caused standard-compliant code to fail to compile and/or compile with a deviant behavior.

In your case, consider:

#include <iostream>

template<typename T>
void func(T& t)
{
    int& r = t;
    ++r;
}

int main(void)
{
    int i = 4;
    long n = 5;
    const int& r = n;

    const int ci = 6;
    const long cn = 7;

    //int& r1 = ci;
    //int& r2 = cn;

    func(i);
    //func(n);

    std::cout << r << std::endl;
}

Which of the commented lines to you want to compile? Do you want func(i) to change its argument and func(n) to NOT do so?

2) You can't make that code compile. You don't want to have that code. A future version of MSVC is probably going to remove the non-standard extension and fail to compile that code. Instead, use a local variable. You can always use a extra pair of braces to control the lifetime of that local variable and cause it to be destroyed prior to the next line of code, just like the temporary would be. Or r-value references.

{
  File ftemp(path, size, off);
  bootrec_reset(ftemp, blksize);
}

3) Yes, you can use C++0x r-value references in this scenario.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • As I see it, your first snippet does not bear on the question of whether or not non-const refs to temporaries should be allowed: I would say that that snippet should not compile solely because (a) there should be no automatic conversion from a const object to a non-const temp rvalue and (b) it should be forbidden to take a non-const ref to a const object. Dunno whether (a) is actually in C++ already, but (b) is. – j_random_hacker Nov 03 '10 at 04:32
  • @j_random_hacker: I was just working on a more complex example. But I don't understand your (a), r-values don't have constness. You want to forbid any automatic conversions from const objects, period? – Ben Voigt Nov 03 '10 at 04:37
  • @Ben: Well my understanding is a bit shaky, but I think rvalues can have (or not have) constness -- though the difference only matters for objects of class type (if the rvalue is const then only const methods may be called on it). And yes I am proposing that no automatic conversion *that would produce a temporary* from a const object should be allowed -- do you see unintended consequences stemming from this perhaps? – j_random_hacker Nov 03 '10 at 05:00
  • I'm confused by your example code for (1). What is the point of ci, cn, r1 and r2? You've got the makings of a good answer but I'm still lost. Can you make what's going on a little more explicit? – beldaz Nov 03 '10 at 10:12
  • @j_random_hacker: Yes, that would break a lot of things. Particularly, I'm glad C++ permits this: `const int i = 5; void func(const long& a); func(i);` Right now a temporary `long` is created by converting `i`, which is `const`. And I don't believe that `const` modifiers on rvalues exist. Can you give an example? – Ben Voigt Nov 05 '10 at 04:45
  • @beldaz: The point is that the proposed change (allowing non-const references to bind to temporaries) would suddenly make all those commented lines compile, and I think every one of them would create highly counter-intuitive behavior. – Ben Voigt Nov 05 '10 at 04:54
  • @Ben Voigt: From the standard 3.10/9: "Class rvalues can have cv-qualified types; non-class rvalues always have cv-unqualified types." Your example could be handled easily by changing `func(i);` to `func(long(i));`, and I think it would aid clarity to require this. – j_random_hacker Nov 05 '10 at 06:40
  • @Ben Voigt: Actually IMHO it would be harmless to continue allowing auto-creation of temporaries to bind to *const* refs, so your example could be left unchanged. The bugs that motivated the "non-const refs can't bind to temporaries" rule only occur for *non-const* refs, so I guess I'm just advocating that non-const refs should be allowed to bind to existing temporaries (with lifetime extension), but that no implicit conversion should be allowed to take place that creates a temporary in this (non-const) case. – j_random_hacker Nov 05 '10 at 06:53
0

How can I force GCC to permit this code?

If you own the definition of File then you can try playing tricks such as this one:

class File /* ... */ {
public:
  File* operator&() { return this; }
/* ... */
};
/* ... */
bootrec_reset(*&File(path, size, off), blksize);

This compiles for me in c++98 mode.

Does the upcoming C++0x standard change this in anyway, or is there something the new standard gives me that is more appropriate here, for example all that jibberish about rvalue references?

Obviously this the way to go if at all possible.

Neil
  • 54,642
  • 8
  • 60
  • 72