Search code examples
c++templatesc++14overloadingrvalue-reference

Overload rvalue and lvalue reference for template deduced type with return value and its implementation


There are a lot of similar questions here on SO (e.g.: How to get different overloads for rvalue and lvalue references with a template-deduced type?), but not exactly this one. In particular, no questions are concered with value returning functions. Furthermore, I am not sure (euphemestically spoken) whether I understood the answers correctly.


I want to provide two versions of a function, which either return a new object or change the passed object (and return the changed object -- for consistency). For simplicity I present my code using a unique like algorithm.

User code

auto vec = std::vector<int>{1,3,5,2,4};
auto vec1 = tt::unique( vec );  
  // does not change vec and returns new vector
  // vec1 should be {1,2,3,4,5}
  // vec should be {1,3,5,2,4}

auto vec2 = tt::unique( std::move(vec) );  
  // does change vec and returns new vector vec2
  // vec2 should be {1,2,3,4,5}
  // vec should be empty

Implementation:

namespace tt {
    template< typename T > T unique( T && vec ) {
        std::sort( vec.begin(), vec.end() );
            vec.erase(
            std::unique( vec.begin(), vec.end()),
            vec.end()
        );
        return std::move( vec );
    }
    template< typename T > T unique( T & vec ) {
        return unique( T(vec) );
    }
}

To avoid code duplication the lvalue reference version calls the rvalue reference version.

My main questions:

  • Is this correct?
  • I think by calling the rvalue reference version from the lvalue reference version, I do one extra move.
  • Apart from this extra move, do I make any other unnecessary constructions?
  • Given the extra move, for very simple algorithms (oneliners), is it more efficient to duplicate the code? E.g. a sort above function I would implement as
    template< typename T > T sort( T & array ) {
        T array_ = array;
        std::sort( array_.begin(), array_.end() );
        return array_;
    }
    
    Is this efficient/correct code?

Extra questions:

  • Why is this overload never used and why does is compile at all, given that c is const.

    template< typename T >
    T unique( const T && c ) {
        std::cout << "Rvalue reference unique wrapper!\n";
        return unique( T(c) );
    }
    
  • Actually I would like to have const in the lvalue reference signature, i.e. something like template<typename T> T unique( const T & vec );. But, as I read, since the first is a forwarding reference, this does not work.

    Thus, what do I have to do to get const correctness here (My question is about C++14, but I am also interrested in C++17 answers).


Solution

  • Through the magic of reference collapsing...

     template<class T>
     [[nodiscard]] std::decay_t<T> unique(T&& t) {
       std::decay_t<T> vec=std::forward<T>(t);
       std::sort( vec.begin(), vec.end() );
       vec.erase(
           std::unique( vec.begin(), vec.end() ),
           vec.end()
       );
       return vec;
    }
    

    works for both cases. DRY.

    Note that view types, like std span, have issues.