Potentially-evaluated expressions

Mar 30, 2023 at 10:04am
An expression is potentially evaluated unless
it is the operand of the sizeof, noexcept, typeid, decltype etc..

https://en.cppreference.com/w/cpp/language/expressions#Potentially-evaluated_expressions

Also

The noexcept operator performs a compile-time check that returns true if an expression is declared to not throw any exceptions

The noexcept operator does not evaluate expression

https://en.cppreference.com/w/cpp/language/noexcept

I'm having difficulties grasping what does it mean to be non-evaluated expression?
For example, if the compiler does compile time check if an expression evaluates to true then how is that expression NOT evaluated?

What does it really mean to be not evaluated?

Another example:

sizeof (a + b)

https://en.cppreference.com/w/cpp/language/sizeof

sizeof does not evaluate therefore a + b, so how does it then know the size of a + b if it does not evalueate it?

Bottom line, what is evaluated expression vs non-evaluated one? and how do these operators return result without evaluating their operands?
Last edited on Mar 30, 2023 at 10:06am
Mar 30, 2023 at 10:58am
I would think it is about the difference of runtime and compile time evaluation.

Things like sizeof (a + b) can be evaluated at compile time.
Everything that may be changed at runtime cannot be evaluated at compile time.
Mar 30, 2023 at 11:48am
Everything that may be changed at runtime cannot be evaluated at compile time

Thus if sizeof(/* runtime expression */) should result in compile time error because sizeof doesn't evaluate expressions, but we know this is not the case.

Is it valid to say that sizeof(/* runtime expression */) results in sizeof of the result of an expression that is evaluated at runtime but not evaluated by the sizeof operator itself?
Mar 30, 2023 at 11:48am
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

int i = 99 ;

// *** warning: expression with side effects has no effect in an unevaluated context
void foo() noexcept( noexcept( sizeof(++i) == 0 ) ) { std::cout << "foo is called\n" ; }

int main()
{
    // *** warning: expression with side effects has no effect in an unevaluated context
    std::cout << std::boolalpha << ( sizeof(++i) > 0 ) << ' ' << noexcept( foo() ) << '\n' ; 

    std::cout << i << '\n' ; // 99 (++i is in unevalated expressions)
}

http://coliru.stacked-crooked.com/a/4ff6981e15ff3886
Mar 30, 2023 at 12:23pm
JLBorges,

This is very good example, I think I get it now.
One issue though, I run MS static analysis which says this:

warning C26447: The function is declared 'noexcept' but calls function 'operator<<<std::char_traits<char> >()' which may throw exceptions (f.6)


Which indicates that foo() is noexcept,
but foo() is NOT noexcept right?

So what is going on with this part?

edit:
noexcept( noexcept( sizeof(++i) == 0 ) ) should evaluate to noexcept(false), so why is it then evaluated to noexcept(true)
Last edited on Mar 30, 2023 at 12:27pm
Mar 30, 2023 at 12:33pm
According to the notes linked, the noexcept expression returns true if the expression inside does not throw. (sizeof(++i) == 0) does not throw.
Mar 30, 2023 at 12:37pm
Ah indeed, void foo() noexcept(sizeof(++i) == 0) is a different story.

Thank you guys, I absolutely get it now, very good example!
Mar 30, 2023 at 12:47pm
To evaluate an expression essentially means that the expression code is executed in order to produce a value.

sizeof just looks at the type of the expression to determine the size. It doesn't need to evaluate the expression because the value is irrelevant.
Last edited on Mar 30, 2023 at 12:48pm
Mar 30, 2023 at 1:41pm
I see:

1
2
3
4
5
6
7
8
9
#include <iostream>

short a = 0;
long b = 1;

int main()
{
	std::cout << sizeof(a + b) << std::endl;
}


returns sizeof(long) but does not add a + b,
a + b could be either compile time or run time expression, so I guess sizeof only takes static type of variables somehow and determines resulting type.

What I still don't understand is at which point it does so?
is it compile time or run time?
Last edited on Mar 30, 2023 at 1:42pm
Mar 30, 2023 at 2:11pm
Yes, you have understood correctly.

The compiler determines the size at compile time.
Mar 30, 2023 at 2:25pm
So of course the compiler looks at the expression to see that it doesn't have any syntax errors and to determine the type, etc. I guess this is a form of "evaluation" using the normal English meaning of the word but in programming "evaluate" has a special meaning so in order to avoid confusion it's best to use different words to describe this.

https://en.wiktionary.org/wiki/evaluate#Verb
Last edited on Mar 30, 2023 at 2:31pm
Mar 30, 2023 at 2:33pm
yes, the language is missing those terms, all kinds of expressions are just called expressions.
Mar 30, 2023 at 2:34pm
What terms is it missing?
Last edited on Mar 30, 2023 at 2:36pm
Mar 30, 2023 at 2:41pm
ex. term of the goal of some expression evaluation,
is it to determine the result, to determine type or size or something else.

Just saying to evaluate sounds like deriving a result of computation.
Last edited on Mar 30, 2023 at 2:41pm
Mar 30, 2023 at 2:43pm
sizeof is evaluated at compile-time (based on the static type of the expression).
(Note: the result of sizeof is a constant expression)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>

struct base { char c[5] {} ; virtual std::size_t size() const noexcept { return sizeof(*this) ; } };

struct derived : base { char c[29] {} ; virtual std::size_t size() const noexcept override { return sizeof(*this) ; } };

void foo( const base& b )
{
    std::cout << "size (compile-time): " << sizeof(b) << '\n' // sizeof(base)
              << "size (run-time)    : " << b.size() << '\n' ; // execute final overrider of the virtual function
}

int main()
{
    const derived d ;
    foo(d) ;
}

http://coliru.stacked-crooked.com/a/6a48f2519f2352ab
Mar 30, 2023 at 2:49pm
Just to be clear, I didn't mean we should avoid the word "evaluate" when talking about finding out the value (and possibly getting the side-effects) of an expression. This is well-established vocabulary in our field. What we should not do is use "evaluate" to mean other things, at least not when talking about things related to expressions, because that would only lead to confusion.
Last edited on Mar 30, 2023 at 3:00pm
Mar 30, 2023 at 2:52pm
I see, this answers all my questions.
thank you for sample code.
Mar 31, 2023 at 9:09am
In sizeof(EXP),
EXP (the expression which forms the operand to sizeof) is unevaluated.
sizeof(EXP) may be evaluated at compile time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

int i = 99 ;

double foo() { ++i ; if( i > 9 ) throw 0 ; return 0 ; } // throws if foo() is evaluated

int main()
{
    // the subexpression foo() * ++i is unevaluated
    // value of sz - sizeof(double) - is evaluated at compile-time
    constexpr std::size_t sz = sizeof( foo() * ++i ) ;
    static_assert( sz == sizeof(double) ) ;
    std::cout << sz << '\n' ;
}

http://coliru.stacked-crooked.com/a/8df73849185b93d3
Mar 31, 2023 at 9:56am
static_assert is a good trick to see if it's evaluated at compile time.
Thank you again for sample, or better to say, a prof.
Mar 31, 2023 at 10:30am
malibor wrote:
static_assert is a good trick to see if it's evaluated at compile time.

It's a good trick to see if an expression can be evaluated at compile time, but it doesn't necessarily mean the same expression will be evaluated at compile time when used in another context.
Topic archived. No new replies allowed.