What is the value category of result yielded from shift operators, bit-wise operators, and sizeof operator?
Asked Answered
J

4

3

Shift operators: << >> bit-wise operators: ~, &, ^, | sizeof operator: sizeof()

Per the C++ standard (n3797), I can only confirm that ~ yields prvalue (5.3.1/2), but not the others above.

Jequirity answered 1/7, 2014 at 20:10 Comment(14)
decltype(sizeof(char))Bushelman
[expr.sizeof]/6 "The result of sizeof and sizeof... is a constant of type std::size_t." But I'm not sure if constant strictly implies prvalue.Sum
@Sum I want to say if it is not specified it is a prvalue, similar to this.Seduction
@user2451677, I am referring to rvalue, lvalue, xvalue instead of the variable type (e.g. int, double)Jequirity
@Sum what I mean is that what was meant although it seems under-specified.Seduction
@ShafikYaghmour Agreed. Some expressions are explicitly specified to yield a prvalue, such as the resolution of CWG 1685. Those who "naturally" return an lvalue are explicitly specified to do so; for the rest, it's likely that a default should apply. In the light of CWG 1642, I still wouldn't say it is currently well-specified, though.Sum
Do you mean unary or binary & or both?Adaptive
@BenVoigt Only the binary one is a bit-wise operator.Sum
@Sum I guess the question is does the usual arithmetic conversions imply prvalue, if that is the case that covers most of these cases.Seduction
operator<<() and operator>>() applied to iostreams definitely returns an lvalue.Adaptive
@BenVoigt, I meant the binary bit-wise &.Jequirity
@ShafikYaghmour True. Although shift operators only require integral promotion, not the full usual arithmetic conversions.Sum
@Sum true, but the same logic should apply.Seduction
@Sum the discussion here has a similar proposal by Jens that arithmetic conversions should imply l to r conversion.Seduction
S
1

As far as I can tell the results are prvalues but that is just a speculative. This seems to be under-specified in a similar way to the value categories of operands which is covered in What is the value category of the operands of C++ operators when unspecified? and Does the standard mandate an lvalue-to-rvalue conversion of the pointer variable when applying indirection?.

We can see from section 3.10 Lvalues and rvalues has the following note:

The discussion of each built-in operator in Clause 5 indicates the category of the value it yields and the value categories of the operands it expects.

but as we can see section 5 only spells out the value category explicitly in a few cases. In the case of this particular question only spells out explicitly the value category of the result for & and ~.

Even though it is underspecified we can find clues that point us in a consistent direction. We can make an argument that the operands for >>, <<, ^ and | are converted to prvalues. This does not dictate the value category of the result but it does seem to exclude the result being an xvalue as per section 5 paragraph 7 which has the following note:

An expression is an xvalue if it is:

— the result of calling a function, whether implicitly or explicitly, whose return type is an rvalue reference to object type,

— a cast to an rvalue reference to object type,

— a class member access expression designating a non-static data member of non-reference type in which the object expression is an xvalue, or

— a .* pointer-to-member expression in which the first operand is an xvalue and the second operand is a pointer to data member.

In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues; rvalue references to functions are treated as lvalues whether named or not.

I don't see any reasonable argument that the result could be an lvalue so that basically leaves us with a prvalue.

So the details are as follows:

Both ~ and & are covered by section 5.3.1 Unary operators paragraph 2 which says:

The result of each of the following unary operators is a prvalue.

The shift operators require the integral promotions which is covered in section 5.8 Shift operators paragraph 1 which says:

The operands shall be of integral or unscoped enumeration type and integral promotions are performed.

and we can see that the integral promotions require prvalues from section 4.5 Integral promotions which says in every paragraph starts with:

A prvalue of [...]

Both ^ and | require the usual arithmetic conversions and both say:

operator applies only to integral or unscoped enumeration operands

and therefore the last clause of the usual arithmetic conversions applies which says:

Otherwise, the integral promotions (4.5) shall be performed on both operands.59

Empirical approach

Luc Danton has an empirical approach to determining the value category of an expression in his answer to Empirically determine value category of C++11 expression?. The approach uses the following code:

template<typename T>
struct value_category {
    // Or can be an integral or enum value
    static constexpr auto value = "prvalue";
};

template<typename T>
struct value_category<T&> {
    static constexpr auto value = "lvalue";
};

template<typename T>
struct value_category<T&&> {
    static constexpr auto value = "xvalue";
};

// Double parens for ensuring we inspect an expression,
// not an entity
#define VALUE_CATEGORY(expr) value_category<decltype((expr))>::value

and the answer outlines the logic as follows:

an lvalue expression results in an lvalue reference type, an xvalue in an rvalue reference type, and a prvalue in just the type.

The following examples all yield prvalue (see it live):

int x = 10, y = 2 ;
int &xr = x ;
int &yr = y ;

std::cout << VALUE_CATEGORY( x << y ) << std::endl ;
std::cout << VALUE_CATEGORY( 10 << 2 ) << std::endl ;
std::cout << VALUE_CATEGORY( xr << yr ) << std::endl ;

std::cout << VALUE_CATEGORY( x | y ) << std::endl ;
std::cout << VALUE_CATEGORY( 10 | 2 ) << std::endl ;

std::cout << VALUE_CATEGORY( x ^ y ) << std::endl ;
std::cout << VALUE_CATEGORY( 10 ^ 2 ) << std::endl ;

std::cout << VALUE_CATEGORY( sizeof( int ) ) << std::endl ;
std::cout << VALUE_CATEGORY( sizeof( x ) ) << std::endl ;
Seduction answered 1/7, 2014 at 20:57 Comment(6)
Integral promotions don't apply to ints. So, strictly speaking, [conv.prom] doesn't require l-to-r for ints - but I think it's not too far-fetched to say that the promotions are intended to produce prvalues for all operands.Sum
In fact, as aschepler pointed out in my own answer, the fact that integral promotion is performed on operands is not sufficient to guarantee that operator<< itself does not make use of some references to temporary objects.Rural
@Sum I updated my answer, let me know if you think it sounds reasonable or if I made any mistakes.Seduction
Howard Hinnant has a different approach to Luc's here, which makes for a interesting read.Seduction
Interestingly, for x += 1, x is converted to a prvalue in the rewritten form x = x + 1 but still the operator yields an lvalue. Similarly, x++. I think the basic idea is that the arithmetic and bitwise operators yield values different from both operands. So it doesn't make sense to return either operand as an lvalue. You create an object with that "new" value by using assignment or explicit construction (of a named entity; also: function parameters).Sum
@Sum yes but the LHS is always an lvalue, same logic used here from defect report 637.Seduction
R
0

sizeof() : size_t according to Standard 5.3.3 pt6 - Standard 5.19/3 states that "An integral constant expression is an expression of integral or unscoped enumeration type, implicitly converted to a prvalue, where the converted expression is a core constant expression." From 5.3.3/6 and 18.2/6 you can deduce that it's a prvalue.

E1 << E2 and E1 >> E2 : Standard 5.8.1 "The operands shall be of integral or unscoped enumeration type and integral promotions are performed. The type of the result is that of the promoted left operand.". According to standard 4.5 and in perticular pt.7, integral promotion implies prvalue.

& | and ^ : Standard specifies that "The usual arithmetic conversions are performed; (...) operator applies only to integral or unscoped enumeration operands".

Rural answered 1/7, 2014 at 20:41 Comment(4)
The operand E1 is promoted, not the expression E1 << E2 or E1 >> E2 we're looking at. And an expression could have the type of a promoted operand without being a prvalue; type and value category are mostly orthogonal.Cardiganshire
@Cardiganshire Is there a standard conversion that produces an lvalue? (other than the identity conversion)Sum
@Sum Not in section 4, though 13.3.3.1.4 considers some reference bindings to count as "conversions". Why?Cardiganshire
@Sum you are right, but what aschepler means is that the operator might be implemented in a way to produce an xvalue, for example by using an expression with a reference to a temporary object. While I would tend to understand xvalue as requiring an expression with an explicit reference (3.10 and 8.3.2), and I would expect CPU shift instructions as prvalues without intermediary references, there is apparently an uncertainty here.Rural
M
-2

cppreference says the following about functions :
prvaule ("pure" rvalue) is an expression that identifies a temporary object (or a subobject thereof) or is a value not associated with any object.
The following expressions are prvalues:

  1. Literal (except string literal), such as 42 or true or nullptr.
  2. Function call/operator expression if the function's or the operator's return type is not a reference, such as str.substr(1, 2) or 2+2

This apply for functions, I'm not sure what exactly are the rules regarding built in operators.

Muscatel answered 1/7, 2014 at 20:26 Comment(3)
Assignment also does not "return a reference" but yields an lvalue. None of those built-in operators are functions.Sum
@eladm26, The increment operator ++ (prefix) does return an lvalue, which seems to contradict thatJequirity
@Sum I see, my mistake, I mixed functions and operators.Muscatel
B
-3

There is small stuff: http://rextester.com/DUEJY28518:

std::cout << typeid(decltype(sizeof(char))).name() << std::endl;
std::cout << typeid(decltype(1 << 1)).name() << std::endl;
std::cout << typeid(decltype(1 >> 1)).name() << std::endl;
std::cout << typeid(decltype(~1)).name() << std::endl;
std::cout << typeid(decltype(1 & 1)).name() << std::endl;
std::cout << typeid(decltype(1 | 1)).name() << std::endl;
std::cout << typeid(decltype(1 ^ 1)).name() << std::endl;

std::cout << "-------------" << std::endl;
std::cout << typeid(decltype(1U << 1)).name() << std::endl;
std::cout << typeid(decltype(1U >> 1)).name() << std::endl;
std::cout << typeid(decltype(~1U)).name() << std::endl;
std::cout << typeid(decltype(1U & 1)).name() << std::endl;
std::cout << typeid(decltype(1U | 1)).name() << std::endl;
std::cout << typeid(decltype(1U ^ 1)).name() << std::endl;

std::cout << "-------------" << std::endl;
std::cout << typeid(decltype(1L << 1)).name() << std::endl;
std::cout << typeid(decltype(1L >> 1)).name() << std::endl;
std::cout << typeid(decltype(~1L)).name() << std::endl;
std::cout << typeid(decltype(1L & 1)).name() << std::endl;
std::cout << typeid(decltype(1L | 1)).name() << std::endl;
std::cout << typeid(decltype(1L ^ 1)).name() << std::endl;

The result is(MSVC):

unsigned int
int
int
int
int
int
int
-------------
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
unsigned int
-------------
long
long
long
long
long
long
Bushelman answered 1/7, 2014 at 20:29 Comment(1)
When you, @rabbit hole digger, edit title of the question, you change the question. Very interisting, isn't ?Bushelman

© 2022 - 2024 — McMap. All rights reserved.