keith
keith

Reputation: 5332

static_cast<> behaviour on class with implicit conversion operator that returns a const reference

I have the following class (stripped down to contain only the relevant parts):

#include <string>

class Text
{
private:
    std::string _text;

public:
    Text(std::string&& text) :
        _text(std::move(text))
    {
    }

    operator const std::string&() const
    {
        return _text;
    }
};

My question is: if I want to obtain a const std::string&, can I do it like this without any penalty:

 Text text("fred");

 auto& s = static_cast<std::string>(text);

Or will this construct an intermediate std::string that I end up getting a reference to? Is there a standard approach to this kind of scenario? I am reasonably new to C++.

Upvotes: 6

Views: 620

Answers (4)

neshkeev
neshkeev

Reputation: 6476

At first, the compiler creates a temp variable in the middle of the cast operation:

int main()
{
    string temp = "foo";
    cout << &temp << endl;
    Text text(move(temp));
    auto s = static_cast<string>(text);
    cout << &s << endl;
    return 0;
}

$ main
0x7fffb9dd7530
0x7fffb9dd7540

As you can see the address of the variable has changed

Secondly, The compiler shadows all the const, volatile etc. modifiers when you use auto

void f (int i) {}

auto s = static_cast<const string&>(text);
f(s);

error: cannot convert 'std::basic_string' to 'int' for argument '1' to 'void f(int)'

So if you need const std::string& you need to apply it to the auto variable:

const auto& s = static_cast<string>(text);
f(s);

error: cannot convert 'const std::basic_string' to 'int' for argument '1' to 'void f(int)'

As you can see the auto keyword shadows the const identifier.

Upvotes: 0

Felix Glas
Felix Glas

Reputation: 15524

No, when you're calling static_cast<std::string>(text), you're calling the implicitly defined copy constructor and creating a temporary object.

However, if you would be calling

auto& s = static_cast<const std::string&>(text);

,then you would correctly be calling the explicit conversion operator operator const Noisy&().

Let's try it out

struct Noisy {
    Noisy() { std::cout << "Default construct" << std::endl; }
    Noisy(const Noisy&) { std::cout << "Copy construct" << std::endl; }
    Noisy(Noisy&&) { std::cout << "Move construct" << std::endl; }
    Noisy& operator=(const Noisy&) { std::cout << "C-assign" << std::endl; return *this; }
    Noisy& operator=(Noisy&&) { std::cout << "M-assign" << std::endl; return *this; }
    ~Noisy() { std::cout << "Destructor" << std::endl; }
};

class Text {
public:
    Text(Noisy&& text) : _text(std::move(text)) {}
    operator const Noisy&() const { return _text; }
private:
    Noisy _text;
};

Test 1

int main() {
    Text text(Noisy{});
    const auto& s = static_cast<Noisy>(text); // Needs 'const' to bind to temporary.
}

Default construct
Move construct
Destructor
Copy construct
Destructor
Destructor

Test 2

int main() {
    Text text(Noisy{});
    auto& s = static_cast<const Noisy&>(text);
}

Default construct
Move construct
Destructor
Destructor

Note: Compiled using option -fno-elide-constructors to avoid copy elision optimizations.

Upvotes: 10

Chris Beck
Chris Beck

Reputation: 16204

I believe the behavior here, when you static_cast to std::string and not const std::string &, is determined by this part of the C++11 standard:

[expr.static.cast] [5.2.9.4]:

An expression e can be explicitly converted to a type T using a static_cast of the form static_cast<T>(e) if the declaration T t(e); is well-formed, for some invented temporary variable t (8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. The expression e is used as a glvalue if and only if the initialization uses it as a glvalue.

If you static_cast to const std::string & then I would expect that no copy would be made. But if you static_cast to std::string, you must get a std::string value out -- it should always be the case that decltype(static_cast<T>(e)) is the same as type T. The only way that a new std::string could be constructed is with the std::string copy ctor, and because your conversion operator is not marked explicit, it is a valid implicit conversion to get a std::string from Text that way.

Or will this construct an intermediate std::string that I end up getting a reference to?

It will construct an intermediate std::string that you end up getting by value.

Upvotes: 1

Puppy
Puppy

Reputation: 146910

That's not even legal C++ for exactly that reason. If you static_cast to something, you will get out exactly that something and nothing else.

Upvotes: 0

Related Questions