C++ 可以从三元运算符引发异常吗?

C++ 可以从三元运算符引发异常吗?,c++,c++11,constexpr,C++,C++11,Constexpr,有时,使用一个只包含一条语句的函数是方便的,甚至是必要的(在返回constexpr时是必要的)。如果需要检查条件并且只允许一条语句,则条件运算符是唯一选项。如果出现错误,最好从条件运算符引发异常,例如: template <typename It> typename std::iterator_traits<It>::reference access(It it, It end) { return it == end? throw std::runtime_err

有时,使用一个只包含一条语句的函数是方便的,甚至是必要的(在返回
constexpr
时是必要的)。如果需要检查条件并且只允许一条语句,则条件运算符是唯一选项。如果出现错误,最好从条件运算符引发异常,例如:

template <typename It>
typename std::iterator_traits<It>::reference
access(It it, It end) {
    return it == end? throw std::runtime_error("no element"): *it;
}
模板
typename std::迭代器_traits::reference
访问(It,It端){
return it==end?throw std::runtime_error(“no元素”):*it;
}
但是,上述函数在用作()时不会编译:

std::vector v;
访问(v.begin(),v.end());

编译器抱怨试图将非常量引用绑定到临时变量。不过,编译器并没有抱怨
throw
-表达式本身。因此,问题是:是否可以从条件运算符引发异常,如果是,上述代码出了什么问题?

可以这样做:

return it == end? (throw std::runtime_error("no element"),*it): *it;

本标准中的措辞约为5.16/2:

如果第二个或第三个操作数的类型为void,则在第二个和第三个操作数上执行左值到右值(4.1)、数组到指针(4.2)和函数到指针(4.3)的标准转换,并且以下之一应保持不变:

-第二个或第三个操作数(但不是两者)是一个抛出表达式(15.1);结果是另一个的类型,是一个PR值


这就解释了你的行为。抛出是合法的,但表达式的类型是纯右值(即使表达式是左值),因此不能绑定非常量左值引用

5.16[expr.cond]中描述了条件运算符。其第2段包括以下案文:

第二个或第三个操作数(但不是两者)是一个抛出表达式(15.1);结果是另一个的类型,是一个PR值

也就是说,允许它从条件运算符抛出异常。然而,即使另一个分支是左值,它也会变成右值!因此,不可能将左值绑定到条件表达式的结果。除了使用逗号运算符重写条件外,还可以重写代码以仅从条件运算符的结果中获取左值:

template <typename It>
typename std::iterator_traits<It>::reference
access(It it, It end) {
    return *(it == end? throw std::runtime_error("no element"): it);
}
模板
typename std::迭代器_traits::reference
访问(It,It端){
return*(it==end?throw std::runtime_error(“no element”):it);
}

有点棘手的事情是,从函数返回
const
引用将编译,但实际上返回对临时函数的引用

它通过修改返回类型进行编译:。@stefan:如果您使函数按值返回,那么它将复制,它不再是同一个函数@DavidRodríguez Dribea是真的,但它消除了编译错误,并揭示了至少该编译器不会因三元运算符出现异常而阻塞。这可以说是一个缺陷,请参见并注意,对于C++14,三元运算符在
constexpr
中失去了其重要性(从Clang 3.4开始,例如on),完全支持它。在
constexpr
中抛出异常与逗号运算符相结合似乎是。这是避免问题的一种方法,但IMO
*(it==end?throw…:it)
看起来更整洁。嘿,这正是Dietmar Kühl在我发布此评论时在自己的答案中所说的@hvd:尽管将解引用移动到outsise看起来更整洁,但它至少在概念上复制了迭代器。使用逗号运算符可以避免复制迭代器。@DietmarKühl你说得对。然而,我并不担心复制成本高昂的迭代器,而且考虑到您按值获取迭代器参数的事实,您似乎也不担心这一点。所以在这种情况下,可读性对我来说有更高的优先级,但是你和其他人可以合理地得出不同的结论。@hvd我认为你/迪特玛的解决方案的真正优势是你不必重复这个表达式。我的解决方案的优点是,它甚至可以用于更复杂的表达式(逗号运算符之后的第一个表达式可以缩短)。也许你应该根据具体情况来决定哪种风格更合适。你应该接受这个答案,因为它回答了问题,同时也提供了对可能陷阱的洞察
template <typename It>
typename std::iterator_traits<It>::reference
access(It it, It end) {
    return *(it == end? throw std::runtime_error("no element"): it);
}