0

Why my const ref becomes invalid in this code and how to avoid this? I can't copy, this is the bottleneck in my application.

class Foo {
public:
    const std::string& string() const {
        return string;
    }

private:
    std::string string = "asdf";
};

Foo foo;
std::vector<std::pair<const std::string&, int>> invalid;
for (int i = 0; i < 5; i++) {
    invalid.emplace_back(std::make_pair(foo.string(), i);
    // after this line invalid[i].first is invalid
}
user3561614
  • 814
  • 1
  • 9
  • 18
  • 4
    Template parameter deduction works in such a way that `make_pair` returns `std::pair` in your call; then `emplace` constructs `std::pair` from that. So the reference is bound to the member of a temporary pair, not to `foo.string`. Make it `invalid.emplace_back(std::pair(foo.string(), i));` – Igor Tandetnik Mar 26 '16 at 13:46
  • How do you know `invalid[i].first` is invalid? `Foo` is not assignable with a move constructor. Is that legal for a vector element? (Genuine question - I haven't caught up with C++11 fully yet.) Does it work if you use `std::pair` as your element type? – Martin Bonner supports Monica Mar 26 '16 at 13:47
  • @IgorTandetnik - Make that an answer so I can upvote it please! – Martin Bonner supports Monica Mar 26 '16 at 13:47
  • 1
    `invalid.emplace_back(foo.string(), i)`. Constructing the pair is superfluous and you also did it wrong. – T.C. Mar 26 '16 at 13:58
  • test your code before posting. – Karoly Horvath Mar 26 '16 at 13:59
  • @IgorTandetnik Works fine now. I would choose your comment as answer, if I could. – user3561614 Mar 26 '16 at 14:33

2 Answers2

1

make_pair is returning a pair<std::string,int>, not a pair<const std::string&, int> because the standard requires it to be that way.

template <class T1, class T2>
constexpr pair<V1, V2> make_pair(T1&& x, T2&& y);

§ 20.3.3 - 8

Returns: pair<V1, V2>(std::forward<T1>(x), std::forward<T2>(y));

where V1 and V2 are determined as follows: Let Ui be decay_t<Ti> for each Ti. Then each Vi is X& if Ui equals reference_wrapper, otherwise Vi is Ui.

This should work according to the standard:

invalid.emplace_back(std::make_pair(std::ref(foo.string()), i));

and this according to me:

invalid.emplace_back(decltype(invalid)::value_type(foo.string(), i));
Gam
  • 1,244
  • 1
  • 8
  • 16
1

Igor Tandetnik already pointed out the problem in your code. FWIW, I don't think it's a good idea to have containers referencing other objects' members by reference in any case - there's an implicit dependence on the relative liftime of objects. You can consider using a shared_ptr to const string, as in the following:

#include <string>
#include <memory>
#include <vector>                                                                                                                           
class Foo {
public:
    const std::shared_ptr<const std::string> string() const {
        return _string;
    }   

private:
    std::shared_ptr<std::string> _string = std::make_shared<std::string>("asdf");
};  

int main()
{   
    Foo foo;
    std::vector<std::pair<std::shared_ptr<const std::string>, int>> invalid;
    for (int i = 0; i < 5; i++) {
        invalid.emplace_back(std::make_pair(foo.string(), i));
    }   
}   
Community
  • 1
  • 1
Ami Tavory
  • 66,807
  • 9
  • 114
  • 153