Martijn Otto
Martijn Otto

Reputation: 938

const-reference qualified member function

The stock example of a reference-qualified member function seems to be something like this:

#include <stdio.h>
#include <stdexcept>
#include <string>

// Easy access to literals
using namespace std::literals;

// File wrapper
class File {
  private:
    //  The wrapped file
    FILE *_file;
  public:
    File(const char *name) : 
        _file(fopen(name, "r")) { 
        // unable to open the file?
        if (!_file) throw std::runtime_error{ "Unable to open file: "s + name };
    } 
    ~File() { 
        fclose(_file);
    } 

    //  Convert to the underlying wrapped file
    operator FILE *() & { 
        return _file;
    } 

    // TODO: Member functions for working with the file
};

This works well. It is not possible to retrieve the underlying FILE pointer from an unnamed temporary directly. However, if we make the casting operator also const-qualified, this no longer seems to work.

Different compilers simply swallow it without complaint even though it's a terribly useful idea. Take, for example the std::string::c_str() member function. You feel it should be reference-qualified (because otherwise you have an invalid pointer) yet it isn't.

Is this a hole in the C++11 standard? Am I missing something here?

Upvotes: 16

Views: 1886

Answers (1)

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 154025

A temporary can be bound to a const& qualified object and the ref-qualifier effectively qualifies the implicitly passed object (*this). If you want to prevent calls on temporaries but allow lvalues, you can = delete the rvalue reference overload and implement the lvalue version. Using const qualified reference qualifiers for both operators requires just one implemented and one = deleted implementation:

class File {
    // ...
    FILE* _file;
public:
    operator FILE*() const&& = delete;
    operator FILE*() const& { return this->_file; }
    // ...
};

The net-effect is that you can use the conversion only for objects to which you go an lvalue:

int main() {
    File       f;
    File const cf{};

    FILE* fp = f;              // OK
    FILE* cfp = cf;            // OK
    FILE* tfp = File();        // ERROR: conversion is deleted
    FILE* mfp = std::move(cf); // ERROR: conversion is deleted  
}

Upvotes: 18

Related Questions