5

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 i.e. 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

4

5 回答 5

8

答案是:你的编译器是错误的。

检查 gcc 或 clang 或类似的,你会得到这样的东西:

prog.cpp:在函数'int main()'中:prog.cpp:9:45:错误:从'std :: vector'类型的右值排序'std :: vector&'类型的非常量引用的无效初始化(向量{2,1,3},向量{3,1,2});^ prog.cpp:6:6: 注意:初始化 'void sort(A&, B&) 的参数 1 [with A = std::vector; B = std::vector]' 无效排序(A& X, B& Y) { }

于 2015-09-22T15:26:39.820 回答
2

您可以使用/Za编译器选项将其变为错误:

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

请注意,/Za过去有很多问题,甚至现在仍然会 break <windows.h>,因此无论如何您都不能将它用于所有编译单元。在 2012 年题为“MSVC /Za 被认为有害”的帖子中,微软高级工程师 Stephan T. Lavavej 甚至建议不要使用该标志,但你也应该看看STL Fixes In VS 2015, Part 2的评论,他说:

我们肯定已经就/Za一致性/Zc选项进行了会议。我们最终希望达到 VC 在默认情况下是一致的,而无需请求额外的选项,从而成为最常用和测试最多的路径。正如您在帖子中看到的那样,我一直在 STL 中通过尽可能删除非标准机器来实现这一目标。

因此,在某些未来版本的 MSVC 中,这很可能是默认的编译错误。


另一件事:C++ 标准不区分错误和警告,它只谈论“诊断消息”。这意味着 MSVC在产生警告时实际上是符合要求的。

于 2015-09-22T16:33:57.357 回答
1

作为你试图解决的 X 问题的答案,而不是你问的 Y 问题……正确的答案是你不应该做你想做的事情。无法想象某事如何有用并不能成为竭尽全力阻止人们做到这一点的充分理由。

而且,事实上,我什至不必抽象地提出这一点:这里有两个具体的例子,其中接受一个临时对象会很有用。

您可能只关心以下两个对象之一:

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

容器不是“独立的”;例如,一个提供另一个视图的容器:

vector<int> A, B;
// fill A and B
sort(make_subsequence(A, 1, 10), make_subsequence(B, 5, 14));
于 2015-09-23T06:46:55.647 回答
1

正如其他答案所指出的,编译器是错误的。

无需更改编译器选项的编译器:

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});

也将无法在 MSVC2013 中编译,并且应该在兼容的编译器中兼容。

请注意,虽然根据标准推导Aand Basconst X是不合法的,但明确传递const XasAB is

最后一种方法是:

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

我们生成一个明确删除的,应该优先于那个A&, B&。我不知道 MSVC 在这种情况下是否正确选择了完美转发的那个,但我希望如此。

于 2015-09-22T17:41:02.783 回答
0

您可以显式地不希望的函数delete重载:sort

#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;
}
于 2015-09-23T06:21:49.490 回答