1262. std::less<std::shared_ptr<T>> is underspecified

Section: 20.3.2.2.8 [util.smartptr.shared.cmp] Status: C++11 Submitter: Jonathan Wakely Opened: 2009-11-10 Last modified: 2016-01-28

Priority: Not Prioritized

View all other issues in [util.smartptr.shared.cmp].

View all issues with C++11 status.

Discussion:

20.3.2.2.8 [util.smartptr.shared.cmp]/5 says:

For templates greater, less, greater_equal, and less_equal, the partial specializations for shared_ptr shall yield a total order, even if the built-in operators <, >, <=, and >= do not. Moreover, less<shared_ptr<T> >::operator()(a, b) shall return std::less<T*>::operator()(a.get(), b.get()).

This is necessary in order to use shared_ptr as the key in associate containers because n2637 changed operator< on shared_ptrs to be defined in terms of operator< on the stored pointers (a mistake IMHO but too late now.) By 7.6.9 [expr.rel]/2 the result of comparing builtin pointers is unspecified except in special cases which generally do not apply to shared_ptr.

Earlier versions of the WP (n2798, n2857) had the following note on that paragraph:

[Editor's note: It's not clear to me whether the first sentence is a requirement or a note. The second sentence seems to be a requirement, but it doesn't really belong here, under operator<.]

I agree completely - if partial specializations are needed they should be properly specified.

20.3.2.2.8 [util.smartptr.shared.cmp]/6 has a note saying the comparison operator allows shared_ptr objects to be used as keys in associative containers, which is misleading because something else like a std::less partial specialization is needed. If it is not correct that note should be removed.

20.3.2.2.8 [util.smartptr.shared.cmp]/3 refers to 'x' and 'y' but the prototype has parameters 'a' and 'b' - that needs to be fixed even if the rest of the issue is NAD.

I see two ways to fix this, I prefer the first because it removes the need for any partial specializations and also fixes operator> and other comparisons when defined in terms of operator<.

  1. Replace 20.3.2.2.8 [util.smartptr.shared.cmp]/3 with the following and remove p5:

    template<class T, class U> bool operator<(const shared_ptr<T>& a, const shared_ptr<U>& b);
    

    3 Returns: x.get() < y.get(). std::less<V>()(a.get(), b.get()), where V is the composite pointer type (7.6.9 [expr.rel]).

    4 Throws: nothing.

    5 For templates greater, less, greater_equal, and less_equal, the partial specializations for shared_ptr shall yield a total order, even if the built-in operators <, >, <=, and >= do not. Moreover, less<shared_ptr<T> >::operator()(a, b) shall return std::less<T*>::operator()(a.get(), b.get()).

    6 [Note: Defining a comparison operator allows shared_ptr objects to be used as keys in associative containers. — end note]

  2. Add to 20.3.2.2 [util.smartptr.shared]/1 (after the shared_ptr comparisons)

    template<class T> struct greater<shared_ptr<T>>;
    template<class T> struct less<shared_ptr<T>>;
    template<class T> struct greater_equal<shared_ptr<T>>;
    template<class T> struct less_equal<shared_ptr<T>>;
    

    Remove 20.3.2.2.8 [util.smartptr.shared.cmp]/5 and /6 and replace with:

    template<class T, class U> bool operator<(const shared_ptr<T>& a, const shared_ptr<U>& b);
    

    3 Returns: xa.get() < yb.get().

    4 Throws: nothing.

    5 For templates greater, less, greater_equal, and less_equal, the partial specializations for shared_ptr shall yield a total order, even if the built-in operators <, >, <=, and >= do not. Moreover, less<shared_ptr<T> >::operator()(a, b) shall return std::less<T*>::operator()(a.get(), b.get()).

    6 [Note: Defining a comparison operator allows shared_ptr objects to be used as keys in associative containers. — end note]

    
    template<class T> struct greater<shared_ptr<T>> :
    binary_function<shared_ptr<T>, shared_ptr<T>, bool> {
      bool operator()(const shared_ptr<T>& a, const shared_ptr<T>& b) const;
    };
    

    operator() returns greater<T*>()(a.get(), b.get()).

    
    template<class T> struct less<shared_ptr<T>> :
    binary_function<shared_ptr<T>, shared_ptr<T>, bool> {
      bool operator()(const shared_ptr<T>& a, const shared_ptr<T>& b) const;
    };
    

    operator() returns less<T*>()(a.get(), b.get()).

    
    template<class T> struct greater_equal<shared_ptr<T>> :
    binary_function<shared_ptr<T>, shared_ptr<T>, bool> {
      bool operator()(const shared_ptr<T>& a, const shared_ptr<T>& b) const;
    };
    

    operator() returns greater_equal<T*>()(a.get(), b.get()).

    
    template<class T> struct less_equal<shared_ptr<T>> :
    binary_function<shared_ptr<T>, shared_ptr<T>, bool> {
      bool operator()(const shared_ptr<T>& a, const shared_ptr<T>& b) const;
    };
    

    operator() returns less_equal<T*>()(a.get(), b.get()).

[ 2009-11-18: Moved to Tentatively Ready after 5 positive votes on c++std-lib. ]

Proposed resolution:

Replace 20.3.2.2.8 [util.smartptr.shared.cmp]/3 with the following and remove p5:

template<class T, class U> bool operator<(const shared_ptr<T>& a, const shared_ptr<U>& b);

3 Returns: x.get() < y.get(). less<V>()(a.get(), b.get()), where V is the composite pointer type (7.6.9 [expr.rel]) of T* and U*.

4 Throws: nothing.

5 For templates greater, less, greater_equal, and less_equal, the partial specializations for shared_ptr shall yield a total order, even if the built-in operators <, >, <=, and >= do not. Moreover, less<shared_ptr<T> >::operator()(a, b) shall return std::less<T*>::operator()(a.get(), b.get()).

6 [Note: Defining a comparison operator allows shared_ptr objects to be used as keys in associative containers. — end note]