I believe this is a deffect in [basic.def.odr]/3 in N4140

234 views
Skip to first unread message

leontro...@gmail.com

unread,
May 12, 2015, 8:59:41 AM5/12/15
to std-dis...@isocpp.org
I'm struggling with this paragraph for almost 2 weeks already. But I believe I have found something inaccurate with the current text in [basic.def.odr]/3 which I repeat below:

A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any nontrivial functions and, if x is an object, ex is an element of the set of potential results of an expression e, where either the lvalue-to-rvalue conversion (4.1) is applied to e, or e is a discarded-value expression (Clause 5).

Consider this simple example:

const int i = 1;
int f(int i) { return i; }
int j = f(i);



According to [basic,def.odr]/3 the variable i is odr-used which is clearly an error. To verify this, note that i  is odr-used unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any nontrivial function. That is true. But the second condition is false, as i is an object, but i is not an element of the set of potential results of the expression f(i), because this set is empty, according to [basic.def.odr]/2. Therefore, we conclude from [basic.def.odr]/3 that i is odr-used.

leontro...@gmail.com

unread,
May 12, 2015, 9:06:53 AM5/12/15
to std-dis...@isocpp.org, leontro...@gmail.com
Pleace replace the reference to x below by i:


According to [basic,def.odr]/3 the variable i is odr-used which is clearly an error. To verify this, note that i  is odr-used unless applying the lvalue-to-rvalue conversion to x yields a constant expression that does not invoke any nontrivial function. That is true. But the second condition is false, as i is an object, but i is not an element of the set of potential results of the expression f(i), because this set is empty, according to [basic.def.odr]/2. Therefore, we conclude from [basic.def.odr]/3 that i is odr-used.

This is the correct sentence:

According to [basic,def.odr]/3 the variable i is odr-used which is clearly an error. To verify this, note that i  is odr-used unless applying the lvalue-to-rvalue conversion to i yields a constant expression that does not invoke any nontrivial function. That is true. But the second condition is false, as i is an object, but i is not an element of the set of potential results of the expression f(i), because this set is empty, according to [basic.def.odr]/2. Therefore, we conclude from [basic.def.odr]/3 that i is odr-used. 

dyp

unread,
May 12, 2015, 9:15:39 AM5/12/15
to std-dis...@isocpp.org
As far as I understand the wording, ex is the id-subexpression i of
the expression f(i), because ex names the variable i. In other words,
the variable i appears as the potentially-evaluated id-expression i.
This id-expression i is an element of the set of potential results of
itself as per [basic.def.odr]p2.1.


Now, I _think_ that the lvalue-to-rvalue conversion is applied to the
subexpression i of the expression f(i) in order to copy-initialize the
function parameter of f. But I cannot find anything in the Standard
that mandates the lvalue-to-rvalue conversion for such kinds of
initialization.


Kind regards,

dyp


On 12.05.2015 14:59, leontro...@gmail.com wrote:
> I'm struggling with this paragraph for almost 2 weeks already. But
> I believe I have found something inaccurate with the current text
> in [basic.def.odr]/3 which I repeat below:
>
> A variable x whose name appears as a potentially-evaluated
> expression ex is odr-used by ex unless applying the
> lvalue-to-rvalue conversion (4.1) to x yields a constant expression
> (5.19) that does not invoke any nontrivial functions and, if x is
> an object, ex is an element of the set of potential results of an
> expression e, where either the lvalue-to-rvalue conversion (4.1) is
> applied to e, or e is a discarded-value expression (Clause 5).
>
> Consider this simple example:
>
> const int i = 1; int f(int i) { return i; } int j = f(i);
>
>
>
> According to [basic,def.odr]/3 the variable i is *odr-used* which
> is clearly *an error*. To verify this, note that i is odr-used
> unless applying the lvalue-to-rvalue conversion to x yields a
> constant expression that does not invoke any nontrivial function.
> That is true. But the second condition is false, as i is an object,
> but i is *not* an element of the set of potential results of the

Leon Trotski

unread,
May 12, 2015, 9:29:14 AM5/12/15
to std-dis...@isocpp.org, dyp...@gmx.net
I believe the e in the second condition of [basic.def.odr]/3 cannot be a sub-expression of another expression, for this condition was put in place to avoid the error shown in DR 712. Therefore the e in [basic.def.odr]/3 refers to f(i) in my example, and not to its sub-expression i

David Krauss

unread,
May 12, 2015, 9:42:22 AM5/12/15
to std-dis...@isocpp.org

On 2015–05–12, at 9:29 PM, Leon Trotski <leontro...@gmail.com> wrote:

I believe the e in the second condition of [basic.def.odr]/3 cannot be a sub-expression of another expression, for this condition was put in place to avoid the error shown in DR 712. Therefore the e in [basic.def.odr]/3 refers to f(i) in my example, and not to its sub-expression i

No, e can be any subexpression. In this case it’s i.

If the function argument was cond? i : i, then i would still not be ODR-used, because the lvalue of i is a potential result of that subexpression too, and the lvalue-to-rvalue conversion is applied by the function call.

The wording change in DR 712 doesn’t mention full-expressions. It clarifies how to reliably process conditional-expressions, which are allowed to “simultaneously” convert multiple variables in this static analysis. More generally, it describes how to trace from any given lvalue-to-rvalue-converted expression to the eligible id-expressions within it.

Leon Trotski

unread,
May 12, 2015, 10:00:15 AM5/12/15
to std-dis...@isocpp.org, pot...@mac.com
By your interpretation of [basic.def.odr]/3 applied to the following example, one would say that the variable i is not odr-used, which is clearly wrong!

const int i = 1;
const int& f(const int& i) { return i; }
int j = f(i);

Richard Smith

unread,
May 12, 2015, 4:11:45 PM5/12/15
to std-dis...@isocpp.org, pot...@mac.com
On Tue, May 12, 2015 at 7:00 AM, Leon Trotski <leontro...@gmail.com> wrote:
By your interpretation of [basic.def.odr]/3 applied to the following example, one would say that the variable i is not odr-used, which is clearly wrong!

const int i = 1;
const int& f(const int& i) { return i; }
int j = f(i);

Here, the lvalue-to-rvalue conversion is not applied to the expression 'i', so the variable 'i' is odr-used.
 
On Tuesday, May 12, 2015 at 10:42:22 AM UTC-3, David Krauss wrote:

On 2015–05–12, at 9:29 PM, Leon Trotski <leontro...@gmail.com> wrote:

I believe the e in the second condition of [basic.def.odr]/3 cannot be a sub-expression of another expression, for this condition was put in place to avoid the error shown in DR 712. Therefore the e in [basic.def.odr]/3 refers to f(i) in my example, and not to its sub-expression i

No, e can be any subexpression. In this case it’s i.

If the function argument was cond? i : i, then i would still not be ODR-used, because the lvalue of i is a potential result of that subexpression too, and the lvalue-to-rvalue conversion is applied by the function call.

The wording change in DR 712 doesn’t mention full-expressions. It clarifies how to reliably process conditional-expressions, which are allowed to “simultaneously” convert multiple variables in this static analysis. More generally, it describes how to trace from any given lvalue-to-rvalue-converted expression to the eligible id-expressions within it.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Andrew Tomazos

unread,
May 12, 2015, 4:19:19 PM5/12/15
to std-discussion, pot...@mac.com
It seems pretty clear by now that this paragraph of the standard is basically unreadable.  I wonder if there is a way to reword it so that it is clearer.  Failing that, perhaps a note and/or an example or two.  -Andrew.

Faisal Vali

unread,
May 12, 2015, 4:31:45 PM5/12/15
to std-dis...@isocpp.org, pot...@mac.com
On Tue, May 12, 2015 at 3:19 PM, Andrew Tomazos <andrew...@gmail.com> wrote:
> It seems pretty clear by now that this paragraph of the standard is
> basically unreadable. I wonder if there is a way to reword it so that it is
> clearer. Failing that, perhaps a note and/or an example or two. -Andrew.

It's also currently a little broken - i think there's a DR that Hubert
recently reported about the invocation of non-trivial functions during
the l-t-rval conversion - so hopefuly during that rewording, either
the wording becomes clearer or additional illustrative examples will
get added (so please continue to be especially nice to core, so they
don't decide to make it even more convoluted ;)

David Krauss

unread,
May 12, 2015, 6:40:31 PM5/12/15
to std-dis...@isocpp.org
On 2015–05–13, at 4:31 AM, Faisal Vali <fai...@gmail.com> wrote:

It's also currently a little broken - i think there's a DR that Hubert
recently reported about the invocation of non-trivial functions during
the l-t-rval conversion

Do you mean the paradox of whether *trivial* copy constructors of literal classes implement l-t-r conversion (so the exemption applies), or merely are implemented by l-t-r conversion (so it does not)?

A non-trivial member function would expose a this pointer, rendering the ODR inescapable.

- so hopefuly during that rewording, either
the wording becomes clearer or additional illustrative examples will
get added (so please continue to be especially nice to core, so they
don't decide to make it even more convoluted ;)

Yes, this paragraph is a good candidate for the single most obtuse in the standard. And it doesn’t help that it’s not obvious how to write test programs to check for ODR-use.

1971 powerChina

unread,
May 12, 2015, 9:17:33 PM5/12/15
to std-dis...@isocpp.org
Title:       The core of the big data solutions -- Map
Author:      pengwenwei
Email:       pw...@foxmail.com
Language:    c++
Platform:    Windows, linux
Technology:  Perfect hash algorithm
Level:       Advanced
Description: A high performance map algorithm
Section      MFC c++ map stl
SubSection   c++ algorithm
License:     (GPLv3)


Map is widely used in c++ programs. Its performance is critical to programs' performance. Especially in big data  and the scenarios which can't realize data distribution and parallel processing.

I have been working on big data analysis for many years in telecommunition and information security industry. The data analysis is so complicated that they can't work without map. Especially in information security industry, the data is much more complicated than others. For example, ip table, mac table, telephone numbers table, dns table etc.


Currently, the STL map and Google's hash map are the most popular maps. But they have some disadvantages. The STL map is based on binary chop, which causes a bad performance. Google Hash map has the best performance at present, but it has probability of collision. For big data analysis, the collision probability is unacceptable.

Now I would like to publish pwwMap. It includes three different maps for different scenarios:
1. Memory Map(memMap): It has a good access speed. But its size is limited by memory size.
2. Harddisk Map(diskMap): It utilizes hard disk to store data. So it could accept much more data than memory map.
3. Hashmap(hashMap): It has the best performance and a great lookup speed, but it doesn't have 'insert' and 'delete' functionality.

MemMap and diskMap could be converted to hashMap by function memMap2HashMap and diskMap2HashMap. According to the test result, my algorithms' collision probability is zero. About performance, memMap has a comparable performance with google, and hashMap's performance is 100 times better than Google's hashmap.

In summary, pwwhash are perfect hash algorithms with zero collision probability. You can refer to following artical to find the key index and compress algorithm theory:
http://blog.csdn.net/chixinmuzi/article/details/1727195

Source code and documents:
https://sourceforge.net/projects/pwwhashmap/files/?source=navbar

--

Faisal Vali

unread,
May 13, 2015, 12:13:15 AM5/13/15
to std-dis...@isocpp.org
On Tue, May 12, 2015 at 5:40 PM, David Krauss <pot...@mac.com> wrote:
>
> On 2015–05–13, at 4:31 AM, Faisal Vali <fai...@gmail.com> wrote:
>
> It's also currently a little broken - i think there's a DR that Hubert
> recently reported about the invocation of non-trivial functions during
> the l-t-rval conversion
>
>
> Do you mean the paradox of whether *trivial* copy constructors of literal
> classes implement l-t-r conversion (so the exemption applies), or merely are
> implemented by l-t-r conversion (so it does not)?

Not sure I am aware of this paradox - could I ask you to elaborate?

I was referring to the following examples that Hubert Tong posted on
the core reflector that will be part of DR2083 in the next core issues
list:

<quote>
The resolution of issue 1741 was not intended to cause odr-use to
occur in cases where it did not do so previously. However, in an
example like

extern int globx;
int main() {
const int &x = globx;
struct A {
const int *foo() {
return &x;
}
} a;
return *a.foo();
}

x satisfies the requirements for appearing in a constant expression,
but applying the lvalue-to-rvalue converstion to x does not yield a
constant expression. Similarly,

struct A {
int q;
constexpr A(int q) : q(q) { }
constexpr A(const A &a) : q(a.q * 2) { }
};

int main(void) {
constexpr A a(42);
constexpr int aq = a.q;
struct Q {
int foo() { return a.q; }
} q;
return q.foo();
}

a satisfies the requirements for appearing in a constant expression,
but applying the lvalue-to-rvalue conversion to a invokes a
non-trivial function.

</quote>



>
> A non-trivial member function would expose a this pointer, rendering the ODR
> inescapable.
>
> - so hopefuly during that rewording, either
> the wording becomes clearer or additional illustrative examples will
> get added (so please continue to be especially nice to core, so they
> don't decide to make it even more convoluted ;)
>
>
> Yes, this paragraph is a good candidate for the single most obtuse in the
> standard. And it doesn’t help that it’s not obvious how to write test
> programs to check for ODR-use.

Yes - not obvious is right!
Message has been deleted
Message has been deleted

Leon Trotski

unread,
May 13, 2015, 3:04:47 PM5/13/15
to std-dis...@isocpp.org, pot...@mac.com
I'm having some problem submitting a new post in this discussion. I ask you the to see this document in My Google Drive.

dyp

unread,
May 13, 2015, 7:21:37 PM5/13/15
to std-dis...@isocpp.org
I think I understand your concern better now, but I don't agree with
your resolution. Here's how I understand [basic.def.odr]p3, in its most
recent form (using a github draft), considering this example:

> struct S {
> static const int a = 1;
> static const int b = 2;
> };
>
> int f(bool x) { return x ? S::a : S::b; }

"A variable x whose name appears as a potentially-evaluated expression
ex is odr-used by ex unless applying the lvalue-to-rvalue conversion
(4.1) to x yields a constant expression (5.20) that does not invoke any
non-trivial functions"

I think we agree on this part: the variables denoted by the names `S::a`
and `S::b` are potentially evaluated in the expression `x ? S::a :
S::b`, and applying the lvalue-to-rvalue conversion to the variables
yields a constant expression that does not invoke any nontrivial functions.


"if x is an object, ex is an element of the set of potential results of
an expression e [...]"

This is where I think we disagree: I interpret this as

If x is an object, an additional condition must be fulfilled to avoid
odr-use: Let S be the set of expressions where ex is a potential result
of. That is, for every expression e in S, ex is a member of the set of
potential results of e. If S contains an element e where either
- the lvalue-to-rvalue conversion is applied to e, or
- e is a discarded-value expression
then x is not odr-used by ex.


Note 0: ex is a specific expression. There can be multiple expressions
all of the form `S::a`, but this paragraph specifically talks about one
instance ex of such an expression.
Note 1: For every element e of the set S, either e is identical with ex,
or ex is a subexpression of e.
Note 2: This (probably) does not cover trivial copy constructors.


Applying this to the example above, to the expression `x ? S::a : S::b`
and the variable denoted by the name `S::a`: The name of the variable
appears as the id-expression `S::a`. The set of expressions where `S::a`
is a member of the potential results is:

S::a // as per 2.1
x ? S::a : S::b // as per 2.6 and 2.1

Of those expressions, neither is a discarded-value expression. But the
lvalue-to-rvalue conversion is (arguably) applied to `x ? S::a : S::b`
in order to initialize the return value of the function f. Therefore,
the variable denoted by `S::a` is not odr-used by the id-expression
`S::a` in this example.

Note that this implies that the function

int const& f(bool x) { return x ? S::a : S::b; }

DOES odr-use `S::a` (and `S::b`), since the lvalue-to-rvalue conversion
is NOT applied to `x ? S::a : S::b` - so there is no element in the set
S that fulfils either condition.


Kind regards,

dyp


On 13.05.2015 20:58, Leon Trotski wrote:
> I believe my first post had a fallacy that I intend to correct now. But
> first I'll show that the *e* mentioned in [basic.def.odr]/3 is the whole
> expression containing the variable *x* for which we want to determine
> whether this variable is odr-used or not. For this, consider the example in
> DR 172:
>
> struct S {
> static const int a = 1;
> static const int b = 2;
> };
>
> int f(bool x) { return x ? S::a : S::b; }
>
> I'll use "reductio ad absurdum" to show this. Let's then assume that e = ex = S::a. Then it follows that S::a is a variable that appears as a potentially-evaluated expression ex = S::a, which yields a constant expression that does not evoke a trivial function. Also, S::a is an object, whose expression, ex, is an element of the set of potential results of the expression e = ex, *but* where the lvalue-to-rvalue conversion is *not* applied to *e* (the expression x ? S::a : S::b is an lvalue) and *e* is not a discarded-value expression. We would then conclude that S::a is odr-used, which clearly is an absurd, for DR 172 was put in place to show exactly the opposite, i.e., that the objects S::a and S::b are not odr-used. That means that the *e* mentioned in [basic.def.odr]/3 can not be a (proper) sub-expression of the expression containing the object, for which we want to determine whether or not, it's odr-used.
>
>
> I'll try to explain now the fallacy in my reasoning in the first post in this discussion. For this I'll rewrite [basic.def.odr]/3, with minor changes (see my emphasis), just to make things clearer, without changing the overall meaning of the paragraph.
>
>
> A variable x whose name appears as a potentially-evaluated expression ex is odr-used by ex unless applying the lvalue-to-rvalue conversion (4.1) to x yields a constant expression (5.19) that does not invoke any nontrivial functions and, *for every object **x* that is an element of the set of potential results of an expression e, then either the lvalue-to-rvalue conversion (4.1) is applied to *e*, or *e* is a discarded-value expression (Clause 5).
>
>
> Now it's clear that the condition "then either the lvalue-to-rvalue conversion (4.1) is applied to *e*, or *e* is a discarded-value expression" has to be satisfied *only* for objects in the expression e that are elements of the set of potential results of *e*. As this set is empty in my first example, this second condition in [basic.def.odr]/3 doesn't need to be satisfied and we conclude that *i* is not odr-used, because it satisfies the first condition above, i.e., that *i* yields a constant expression and does not invoke any non trivial function.
>
>
> In summary, the paragraph seems to be OK, once we assume that the *e* mentioned in it, is the entire expression containing the variable *x*.
>

Richard Smith

unread,
May 13, 2015, 8:24:48 PM5/13/15
to std-dis...@isocpp.org
Right. Another way to put this:

If x is an object, then consider the set E of expressions (containing ex) that are either discarded-value expressions or to which the lvalue-to-rvalue conversion is applied. If there exists an expression e within E such that ex is in the set of potential results of e, then ex does not odr-use x.

>

David Krauss

unread,
May 13, 2015, 10:08:01 PM5/13/15
to std-dis...@isocpp.org
On 2015–05–13, at 12:13 PM, Faisal Vali <fai...@gmail.com> wrote:

On Tue, May 12, 2015 at 5:40 PM, David Krauss <pot...@mac.com> wrote:

Do you mean the paradox of whether *trivial* copy constructors of literal
classes implement l-t-r conversion (so the exemption applies), or merely are
implemented by l-t-r conversion (so it does not)?

Not sure I am aware of this paradox - could I ask you to elaborate?

I was thinking of the same issue as DR1741. Maybe it’s better phrased there.

It looks to me like DR1741 also completely handles nontrivial functions. In constexpr function evaluation since C++11, runtime execution is supposed to be viable, but when the ODR forbids using a runtime object the implementation is forced to get the result at compile time.

The phrasing of the resolution seems to point in that direction, to me at least. It would be nice if more rationale were recorded.

I was referring to the following examples that Hubert Tong posted on
the core reflector that will be part of DR2083 in the next core issues
list:

The first example is confusing because no l-to-r conversion is ever applied to the expression x. It’s applied to *a.foo(), which isn’t a constant expression because foo isn’t constexpr. If it were constexpr, then constant evaluation would be OK up to that last l-to-r, which fails because there’s no initialization for globx. I seem to be missing the point because it looks straightforward. Also, I don’t see the point of A or a.

The second example is ill-formed because a is not in scope inside Q::foo. Disregarding this, again, no lvalue-to-rvalue conversion is ever applied to a, but only to a.q. If there were an expression invoking the nontrivial copy constructor of A, it should generate an ODR-use compared to C++11 because of the rationale I extrapolated from DR1741. Perhaps Hubert was in the room and they said something about not adding ODR-uses relative to C++11, but such cases were definitely missing.

Leon Trotski

unread,
May 14, 2015, 11:11:39 AM5/14/15
to std-dis...@isocpp.org, dyp...@gmx.net
You wrote this and I think I understood what you meant.

If x is an object, an additional condition must be fulfilled to avoid odr-use: Let S be the set of expressions where ex is a potential result of. That is, for every expression e in S, ex is a member of the set of  potential results of e. If S contains an element e where either 
  - the lvalue-to-rvalue conversion is applied to e, or 
  - e is a discarded-value expression 
then x is not odr-used by ex.

You're saying that if we can show just one element of the set S that satisfies one of the conditions above, then the object x is not odr-used (assuming of course that x satisfies the first condition stated in the paragraph, i.e., that it is a constant expression). I just can't see how can you reach this conclusion (your interpretation of the text), just by reading the Standard?

But even if we accept this interpretation as valid, there's still the problem you mentioned on your first comment above, about the fact that the Standard doesn't seem to support the lvalue-to-rvalue conversion of the variable i in my first example when it's passed by-value to the function f, which is a necessary condition, according to your interpretation, for this variable to be considered not odr-used by the expression f(i)

dyp

unread,
May 14, 2015, 9:01:17 PM5/14/15
to std-dis...@isocpp.org
As far as I understand, the [basic.def.odr]p3 tries to codify in
Standardese the following concept:

Objects are regions of storage and can contain values. Let x be an
object, and ex be an expression that names this object. If the compiler
knows the value stored within x at the point where ex is evaluated, it
does not need to read this value from the object. Therefore, if we only
use ex to read the value of x, the object (the region of storage) x does
not need to exist. Similarly, if the compiler knows that the value of ex
is discarded, the region of storage x does not need to exist.

This has been restricted to values known at compile-time; specifically
to values that all conforming compilers must be able to compute (know)
at compile-time. This restriction is probably for portability.

Another way to formulate the concept: If we do not need the address of
x, and if we do not need to store something within x, then we do not
need a region of storage (with a unique address).

There is an additional restriction: The value of x has to be read
"immediately" from ex. For example:

{
auto* p = &ex;
auto c = *p; // indirect read
}

I would guess this too is to keep things simple for implementers, and
therefore allow practical portability. Imagine a less restrictive rule,
where some compilers require a definition of x in the above example
because they're unable to determine that p is only used to read the
value of x. Consider a situation where the address of x is required:

auto* p = &ex;
std::cout << (void*)p; // unique address is guaranteed

Here, we do need the region of storage x for its unique address.

The lvalue-to-rvalue conversion reads the value of an object (lvalues
are always objects). So when the lvalue-to-rvalue conversion is applied
directly to ex, and ex is a compile-time constant, we do not need the
object x (we do not odr-use x). In other contexts, the object x can be
required. The definition of x is NOT required if it is NEVER odr-used.

If the l-t-r conv is not directly applied to ex, maybe it is applied to
an expression that ex is part of? Assume ex and ey are of type const int:

42 + (b ? ex : ey)

The l-t-r conversion is not applied directly to ex, but it is not very
hard for the compiler to see that in order to compute the value of the
complete expression, we only need the value of ex, but not the object x.
This is what CWG 712 is about: There are cases that are simple enough
that do not want to enforce the existence of a definition of x, even
though they do not immediately read the value of x via the expression ex.

These cases have been specified by looking at the (sub)expressions e
that contain ex. We start from ex and move towards the complete
expression c. If we can easily determine that e either discards the
value of x or only reads the value of x, then we're done: we know that
this occurrence of ex does not require an object x. Otherwise, we look
at the expression e' that e is part of, and ask again: can we easily
determine that e' reads from x or discard its value?
We will end up either at an expression that is too complicated (so we
require a definition), or we have already determined that we don't need
x here.

> I just can't see how
> can you reach this conclusion (your interpretation of the text), just
> by reading the Standard?

The Standard says: "ex is an element of the set of potential results of
an expression e [, where e fulfils the following criteria ...]"
If ex is an element of the set of potential results of ANY such
expression e - call it e' - than this condition is fulfilled:

ex is an element of the set of potential results of the expression e',
and e' fulfils the criteria

The Standard doesn't say "[...] of a complete expression e", so we are
potentially looking at subexpressions. Additionally, if ALL expressions
that ex is part of had to fulfil the criteria, then it should read "ex
is an element of the set of potential results of ALL expressions e it is
part of". So I concluded: just one such expression e is sufficient.


On the topic of initialization vs lvalue-to-rvalue conversion: Some time
ago (C++11), I would have said that some forms of initialization are
supposed to apply the lvalue-to-rvalue conversion, such as:

int x;
int y = x; // causes l-t-r

because the l-t-r at that time was the only place where it said that
reading from an uninitialized object causes Undefined Behaviour. But
with the resolution of CWG 1787, this has changed:

https://github.com/cplusplus/draft/commit/426888a7fdd7d8ee6aa1aef60f1f5e3526faaf6c

Now, the UB in the above example is introduced by _an evaluation that
produces an indeterminate value_. The lvalue-to-rvalue conversion is no
longer required for the UB. We do know from [dcl.init]p17.8 that we need
to extract the value from x, but as far as I know, the Standard does not
strictly require the lvalue-to-rvalue conversion to read the value from
an object.

This missing piece otherwise seemed to be purely a problem for
language-lawyers, but now it seems that [basic.def.odr]p3 requires this
missing piece in order to codify the concept I've mentioned above: if
these kinds of initialization do not require l-t-r conversion, then:

struct S {
constexpr static short M = 42;
};

int x = M; // odr-use of M
int y = 0 + M; // no odr-use of M


And this, to me, does not seem to be the intention of [basic.def.odr]p3.



Kind regards,

dyp


On 14.05.2015 17:11, Leon Trotski wrote:
> You wrote this and I think I understood what you meant.
>
> If x is an object, an additional condition must be fulfilled to
> avoid odr-use: Let S be the set of expressions where ex is a potential
> result of. That is, for every expression e in S, ex is a member of the set
> of potential results of e. If S contains an element e where either
> - the lvalue-to-rvalue conversion is applied to e, or
> - e is a discarded-value expression
> then x is not odr-used by ex.
>
> You're saying that if we can show *just one* element of the set S that
> satisfies one of the conditions above, then the object x is not odr-used
> (assuming of course that x satisfies the first condition stated in the
> paragraph, i.e., that it is a constant expression). I just can't see how
> can you reach this conclusion (your interpretation of the text), just by
> reading the Standard?
>
> But even if we accept this interpretation as valid, there's still the
> problem you mentioned on your first comment above, about the fact that the
> Standard doesn't seem to support the lvalue-to-rvalue conversion of the
> variable *i* in my first example when it's passed by-value to the function
> *f*, which is a necessary condition, according to your interpretation, for
> this variable to be considered not odr-used by the expression *f(i)*.
>

Leon Trotski

unread,
May 15, 2015, 3:18:50 PM5/15/15
to std-dis...@isocpp.org, dyp...@gmx.net
This was an outstanding answer and I want to thank you for your patience in clarifying all the small details that I was concerned with in relation to [basic.def.odr]/3. Also, I want to congratulate you on the quality and rigor of all your comments on this discussion.

But there's still one thing that I'm missing: why don't you (or anyboby else) submit an issue to CWG about the necessity of the l-t-r conversion, for the cases shown below (and above)?

Leon Trotski

unread,
May 17, 2015, 6:04:44 PM5/17/15
to std-dis...@isocpp.org, dyp...@gmx.net
Paragraph §4/3 and §4/6 in C++14, don't they answer your concern about the possible lack of a specification for an lvalue-to-rvalue conversion, when built-in types are initialized, as you mentioned below?


On Thursday, May 14, 2015 at 10:01:17 PM UTC-3, dyp wrote:

dyp

unread,
May 17, 2015, 7:52:58 PM5/17/15
to Leon Trotski, std-dis...@isocpp.org
To me, [conv]p3 and p6 aren't explicit enough to conclude that
initialization in those cases requires an l-t-r conversion. For example,
p6 says "The effect of any implicit conversion is the same as performing
the corresponding declaration and initialization and then using the
temporary variable as the result of the conversion". But we're not using
the initialized variable in an expression when simply initializing it:

int x = 42;
int y = x; // y is not used!

So there is no expression that could be a prvalue.

There are other hints that l-t-r should be applied, e.g. the value
categories [basic.lval]p1 imply that only prvalues can be "values".

C11 (N1570) has the similar lvalue conversion in §6.3.2.1p2:

Except when it is the operand of the sizeof operator, the _Alignof
operator, the unary & operator, the ++ operator, the -- operator, or the
left operand of the . operator or an assignment operator, an lvalue that
does not have array type is converted to the value stored in the
designated object (and is no longer an lvalue); this is called lvalue
conversion.

However, in C++ the situation might be more complicated, as David
Krauss' remark shows. I came up with the following related issue:

struct trivial
{
trivial() = default;
trivial(trivial const&) = default; // or even trivial&
trivial(trivial&&) = delete;
};
struct nontrivial
{
nontrivial() = default;
nontrivial(nontrivial const&) {}
};

struct enclosing
{
static constexpr int i = {};
static constexpr trivial t = {};
static constexpr nontrivial n = {};
};

auto j = enclosing::i; // l-t-r applied?
auto u = enclosing::t; // l-t-r applied?
auto m = enclosing::n; // l-t-r applied?

In second initialization `auto u = ..`, a trivial copy constructor is
called. I don't see how an l-t-r conversion could apply here - I can
only think that this kind of initialization is considered _equivalent_
to an lvalue-to-rvalue conversion, even though the initialization target
is not a temporary.

Neither clang++ nor g++ require a definition for i nor t, but both
require a definition for n (this looks like CWG 1741).


Additionally, there is the issue of CWG 1642, and this SO discussion on
the topic:

http://stackoverflow.com/q/14935722/

The second answer (by Johannes Schaub) seems to use the same approach
you're suggesting, while the first answer (by Andy Prowl) uses a lot of
reading between the lines, conjectures etc. It's just too vague for me
to be a definite answer. Something this simple should be easy to
understand completely and easy to explain. Even if everyone agrees that
the l-t-r is applied in those cases, I would appreciate some clarification.


Kind regards,

dyp

Leon Trotski

unread,
May 18, 2015, 1:42:27 PM5/18/15
to std-dis...@isocpp.org, dyp...@gmx.net
Thanks for your reply. You said:

"Neither clang++ nor g++ require a definition for i nor t, but both 
require a definition for n (this looks like CWG 1741)."

For the C++14 version of the compilers a definition is not required for i, t and n. See this example.

dyp

unread,
May 18, 2015, 1:47:29 PM5/18/15
to std-dis...@isocpp.org
I was surprised for a moment, since I did check this on coliru before
sending my prior mail. The difference between your test and mine is the
optimization level. If you turn off optimizations, both coliru's clang++
and g++ do report a linker error: a missing definition of enclosing::n

(Violations of the ODR are ill-formed with No Diagnostic Required.)


Kind regards,

dyp
> <http://coliru.stacked-crooked.com/a/57b48d704b733ead>.
>
Reply all
Reply to author
Forward
0 new messages