firetto
firetto

Reputation: 13

Using std::weak_ptr::lock() in std::tie

I have a struct that contains two weak pointers, and I wish to compare this struct lexicographically using std::tie. However, I ran across a strange issue where I realized that I can't use std::weak_ptr::lock() as an argument of std::tie. Example code:

struct S {
    S(std::shared_ptr<int>& f, std::shared_ptr<int>& s) {
        first = f;
        second = s;
    }
    bool operator<(const S& rhs) const {
        return std::tie(first.lock(), second.lock()) < std::tie(rhs.first.lock(), rhs.second.lock());
    }
    std::weak_ptr<int> first, second;
};

Doing this results in a compilation error with a code of E0304: no instance of function template "std::tie" matches the argument list.

However, if I create new std::shared_ptr objects and set them to the values of std::weak_ptr::lock() and then compare them, everything works just fine:

struct S {
    S(std::shared_ptr<int>& f, std::shared_ptr<int>& s) {
        first = f;
        second = s;
    }
    bool operator<(const S& rhs) const {

        // creating shared_ptrs and assigning them the value of std::weak_ptr::lock()

        std::shared_ptr<int> 
            f = first.lock(), s = second.lock(),
            rf = rhs.first.lock(), rs = rhs.second.lock();

        // compare those shared_ptrs
        return std::tie(f, s) < std::tie(rf, rs);
    }
    std::weak_ptr<int> first, second;
};

int main() {
    std::shared_ptr<int> 
        a(new int(10)),
        b(new int(5));

    // just two S initializations
    S foo(a, b);
    S bar(b, a);

    if (foo < bar) {
        std::cout << "Foo is less than Bar";
    }
    else {
        std::cout << "Otherwise";
    }

}

Output: Foo is less than Bar

Any reason as to why this may be? Thank you!

Upvotes: 0

Views: 138

Answers (2)

aschepler
aschepler

Reputation: 72271

std::tie is for lvalues only, creating return types such as std::tuple<T1&, T2&>. The lock() member function returns a shared_ptr by value, so the expression is an rvalue and cannot be used with std::tie.

You can use the same tuple lexicographical trick more generally by writing std::forward_as_tuple instead:

return std::forward_as_tuple(first.lock(), second.lock()) < 
    std::forward_as_tuple(rhs.first.lock(), rhs.second.lock());

The return type of these forward_as_tuple calls will be std::tuple<std::shared_ptr<int>&&, std::shared_ptr<int>&&>. forward_as_tuple is a bit more dangerous than tie in general since it can in some uses create dangling references with no compiler warnings, but it's safe here since the tuple objects are temporary and will not be used after the operator< evaluation.

Upvotes: 2

kmdreko
kmdreko

Reputation: 59952

std::tie takes in arguments by reference since it builds a tuple of references from them. Temporaries, like the std::shared_ptr returned by lock(), cannot be bound to references. Creating separate variables gives the references something to bind to.

Upvotes: 3

Related Questions