简体   繁体   中英

Having a function only accept non-const lvalues

I have a function which sorts two vectors with the first of them as ordering criterion. Its signature is

template<typename A, typename B>
void sort(A&& X, B&& Y)
{
  ..
}

The problem is that universal references would allow nonsense cases like

sort(vector<int>{ 2,1,3 }, vector<int>{ 3,1,2 });

where an rvalue will be destroyed afterwards (nonsense).

Asking explicitly for a lvalue doesn't work since

template<typename A, typename B>
void sort(A& X, B& Y) ... // (*)

sort(vector<int>{2,1,3}, vector<int>{3,1,2});

for some reason the above compiles (I thought only const lvalues were allowed to bind to rvalues and to prolong their lifetime?).

If I add const to the lvalue reference then the function will no longer be able to modify the vectors and sort them.


My questions are:

1) Why in the example marked with // (*) can I bind a rvalue to a lvalue that is not even const ? Why instead something like int& r = 20; isn't allowed? What's the difference?

2) How can I solve my issue ie having the function accept only lvalues and not rvalue temporaries? (If it's possible, of course)

Obviously I'm allowed to use any C++ version available

The answer is: your compiler is wrong.

Check on gcc or clang or similar and you'll get something like this:

prog.cpp: In function 'int main()': prog.cpp:9:45: error: invalid initialization of non-const reference of type 'std::vector&' from an rvalue of type 'std::vector' sort(vector{2,1,3}, vector{3,1,2}); ^ prog.cpp:6:6: note: initializing argument 1 of 'void sort(A&, B&) [with A = std::vector; B = std::vector]' void sort(A& X, B& Y) { }

You can use the /Za compiler option to turn this into an error:

error C2664: 'void sort<std::vector<int,std::allocator<_Ty>>,std::vector<_Ty,std::allocator<_Ty>>>(A &,B &)' : cannot convert argument 1
from 'std::vector<int,std::allocator<_Ty>>' to 'std::vector<int,std::allocator<_Ty>> &'
        with
        [
            _Ty=int
,            A=std::vector<int,std::allocator<int>>
,            B=std::vector<int,std::allocator<int>>
        ]
        and
        [
            _Ty=int
        ]
        and
        [
            _Ty=int
        ]
        A non-const reference may only be bound to an lvalue

Note that /Za has had quite some issues in the past and even nowadays still breaks <windows.h> , so you cannot use it for all compilation units anyway. In a 2012 posting titled "MSVC /Za considered harmful" , Microsoft senior engineer Stephan T. Lavavej even recommends not using the flag, but you should also have a look at the comments at STL Fixes In VS 2015, Part 2 , where he says:

We've definitely had meetings about the /Za and /Zc conformance options. We ultimately want to get to a point where VC is conformant by default, without having to request extra options, so that becomes the most-used and most-tested path. As you can see in the post, I've been working towards this in the STL by removing non-Standard machinery whenever possible.

So, chances are this will be a compilation error by default in some future version of MSVC.


One other thing: The C++ standard does not distinguish between errors and warnings, it only talks about "diagnostic messages". Which means that MSVC actually is conforming as soon it produces a warning.

As noted by other answers, the compiler is wrong.

Without having to change compiler of compiler options:

struct sfinae_helper {};
template<bool b>
using sfinae = typename std::enable_if<b, sfinae_helper>::type*;
// sfinae_helper, because standard is dumb: void*s are illegal here

template<class A, class B,
  sfinae<!std::is_const<A>::value&&!std::is_const<B>::value> = nullptr
>
void sort(A& X, B& Y) ... // (*)

sort(vector<int>{2,1,3}, vector<int>{3,1,2});

will fail to compile in MSVC2013 as well, and should be compliant in compliant compilers.

Note that while deducing A and B as const X is not legal under the standard, explicitly passing const X as A or B is .

A final approach is:

template<typename A, typename B>
void sort(A& X, B& Y) ... // (*)
template<typename A, typename B>
void sort(A&& X, B&& Y) = delete;

where we generate an explicitly deleted one that should be preferred to the A&, B& one. I do not know if MSVC properly picks the perfectly forwarded one in that case, but I hope so.

As an answer to the X problem you're trying to solve rather than the Y problem you asked... the right answer is that you shouldn't do what you're trying to do. Being unable to imagine how something can be useful is not an adequate reason to go out of your way to prevent people from being able to do it.

And, in fact, I don't even have to suggest this in the abstract: here are two concrete examples where accepting a temporary object would be useful.

You might only care about one of the two objects:

interesting_container A;
// fill A
sort(an_ordering_criterion(), A);

The containers aren't 'self-contained'; eg a container that provides a view into another one:

vector<int> A, B;
// fill A and B
sort(make_subsequence(A, 1, 10), make_subsequence(B, 5, 14));

You can explicitly delete undesired overloadings of sort function:

#include <iostream>
#include <vector>

#include <cstdlib>

template< typename X, typename Y >
void
sort(X &, Y &)
{
    static_assert(!std::is_const< X >{});
    static_assert(!std::is_const< Y >{});
}

template< typename X, typename Y >
int
sort(X const &, Y &) = delete;

template< typename X, typename Y >
int
sort(X &, Y const &) = delete;

template< typename X, typename Y >
int
sort(X const &, Y const &) = delete;

int
main()
{
    std::vector< int > v{1, 3, 5};
    std::vector< int > const c{2, 4, 6};
    ::sort(v, v); // valid
    { // has been explicitly deleted
        //::sort(v, c); 
        //::sort(c, v);
        //::sort(c, c);
    }
    { // not viable: expects an l-value for 1st argument
        //::sort(std::move(v), v); 
        //::sort(std::move(v), c);
        //::sort(std::move(c), v);
        //::sort(std::move(c), c);
    }
    { // not viable: expects an l-value for 2nd argument
        //::sort(v, std::move(v));
        //::sort(v, std::move(c));
        //::sort(c, std::move(v));
        //::sort(c, std::move(c));
    }
    { // not viable: expects an l-value for 1st or 2nd argument
        //::sort(std::move(v), std::move(v));
        //::sort(std::move(v), std::move(c));
        //::sort(std::move(c), std::move(v));
        //::sort(std::move(c), std::move(c));
    }
    return EXIT_SUCCESS;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM