2

A constexpr function accepts only parameters, which are of a literal type. An object of the class string is not a literal type itself, but a pointer (which is a scalar type) or a reference is a literal type. And therefore a reference to string is a literal type.

Furthermore a constexpr function returns only a constant expression, if it's arguments are also constant expressions.

"The scale* function will return a constant expression if its argument is a constant expression but not otherwise:"
Source: The C++ Primer, 5th edition

scale is in this case is_shorter()

Now I'm passing here two references to string to a constexpr function, which doesn't remain at a fixed address (global or static) but at a variable address (local). Therefore both arguments shouldn't be constant expressions.
The result of the constexpr function is assigned to a constexpr bool.

#include <iostream>
#include <string>
#include <type_traits>
using namespace std;

/*
 * it isn't possible to define is_shorter as constexpr function
 * 1. string is not a literal type, but can be passed as pointer or reference,
 *    which are literal types.
 * 2. the member function size() is not a constexpr function
 *
 */

// compare the length of two strings
constexpr bool is_shorter(const string& s1, const string& s2) {
    // compiler message from gcc: 'error: call to non-constexpr function'
    // return s1.size() < s2.size();

    // compare addresses of pointers, which is legal or illegal but useless, because
    // both parameters are local objects and not stored at fixed addresses.
    return &s1 != &s2;
}

int main() {
    string shrt = "short";
    string longer = "longer";

    cout << "address of shrt " << &shrt << "\n";
    cout << "address of longer " << &longer << "\n";

    constexpr bool state = is_shorter(shrt, longer);
    if (state) {
        cout << "shrt is shorter\n";
    } else {
        cout << "longer is longer\n";
    }

    cout << "string " << is_literal_type<string>::value << "\n";
    cout << "string& " << is_literal_type<string&>::value << "\n";
    cout << "string* " << is_literal_type<string*>::value << "\n";
    return 0;
}   

Compile:

$ g++ -o ex646 ex646.cpp -std=gnu++11 -Wall -Wpedantic

Run:

$ ./ex646
address of shrt 0x7ffd39e41a50 # every time different
address of longer 0x7ffd39e41a40 # every time different, always 0x10 more than &shrt
shrt is shorter
string 0
string& 1
string* 1

How is the compiler able to compare the addresses of both strings? They are during every run-time of the program different, whereas their relative position remains constant. Is the key to this usage, that at least their relative position to each other remains constant?

Update 2015-08-12
As time of writing this looks like in 'a bug in the constant evaluation rules' of the ISO-Standard (correct me if I'm wrong). See this discussion on an isocpp.org list. And therefore this shouldn't be a compiler bug inside GCC and CLANG.

Thanks dyp!

Peter
  • 2,240
  • 3
  • 23
  • 37
  • If you want to compare two c-string literals (i.e. *not* `std::string`) at compile time to see which is shorter, it is possible. Just about. Is this your ultimate goal? Anyway, It's a pity that `std::string` isn't a literal type. – Aaron McDaid Aug 10 '15 at 20:26
  • @AaronMcDaid the names are a little unfortunate but the base question is still a good one and I think it might help the question to choose some better names. – Shafik Yaghmour Aug 10 '15 at 20:28
  • ""The `scale` function will return a constant expression if its argument is a constant expression but not otherwise:"* This is overly restrictive. The arguments don't have to be constant expressions, the whole function call has to *produce* a constant expression. – dyp Aug 10 '15 at 20:57
  • That sounds reasonable, the description of a constexpr function at cppreference.com[1] doesn't sound like it requires "constant expression" as paramters, literal types are enough. The C++11 (N3337) Standard uses nearly the same wording. [1] en.cppreference.com/w/cpp/language/constexpr – Peter Aug 10 '15 at 23:38

2 Answers2

3

So as far as I can tell this looks like some sort of compiler extension or a bug. C++11 was adjusted via a defect report to allow references to be considered literal types regardless if the variable it references is a literal type. But, a reference constant expression should refer to an object of static storage duration and similar for a address constant expression.

The draft C++11 standard tell us what cases pointers can be considered equal:

Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (3.9.2).

so in the your case the compiler can trivially deduce which case the != falls under but I don't see a carve out that it would let it avoid the constant expressions requirements for this case.

For reference section 5.19 [expr.const] tell us:

an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization, initialized with a constant expression

this was modified by defect report 1454 which changes to read as follows:

an id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization and either

  • it is initialized with a constant expression or
  • it is a non-static data member of an object whose lifetime began within the evaluation of e

but in either case that should be sufficient to make this case not a constant expression.

A reference constant expression is an lvalue core constant expression that designates an object with static storage duration or a function. An address constant expression is a prvalue core constant expression of pointer type that evaluates to the address of an object with static storage duration, to the address of a function, or to a null pointer value, or a prvalue core constant expression of type std::nullptr_t.

none of which applies to the automatic variables in your case.

References were initially not literals but defect report 1195 which says:

7.1.5 [dcl.constexpr] paragraph 3 is overly restrictive in requiring that reference parameter and return types of a constexpr function or constructor must refer to a literal type. 5.20 [expr.const] paragraph 2 already prevents any problematic uses of lvalues of non-literal types, and it permits use of pointers to non-literal types as address constants. The same should be permitted via reference parameters and return types of constexpr functions.

and changes section 3.9 [basic.types] which said in the N3337 the draft C++11 standard:

A type is a literal type if it is:

[...]

  • a reference type referring to a literal type; or

to:

  • a reference type; or
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • I originally deleted my answer after answering because I misread it but then realized something else odd was going on and updated an undeleteded. – Shafik Yaghmour Aug 10 '15 at 20:38
  • @dyp well if the references are not constant expressions then the addresses can not be either., at least as far as I can tell. – Shafik Yaghmour Aug 10 '15 at 21:21
  • The idea of a compiler extension looked promising to me. So I've compiled my code from above with "clang++ -std=c++11 -pedantic -Werror -o ex646 ex646.cpp" (no errors, no warnings, runs) and "g++ -o ex646 ex646.cpp -std=c++11 -Wall -Wpedantic" (no errors, no warnings, runs). That doesn't looks like a intentional extension. – Peter Aug 10 '15 at 21:54
  • @dyp sorry, I did not get a chance to reply earlier but I believe the bullet that says *id-expression that refers to a variable or data member of reference type unless the reference has a preceding initialization, initialized with a constant expression* is sufficient to make them not constant expressions. – Shafik Yaghmour Aug 11 '15 at 13:00
  • @dyp well `5.10` says *both represent the same address* which sounds like the addresses are required but it could be argued it is not totally clear. – Shafik Yaghmour Aug 11 '15 at 13:03
  • @Peter well it is not always clear if the compiler developers consider them extensions or whether something is accidental. We can see [here](http://stackoverflow.com/a/31526370/1708801) that there is 2 year old bug report with no response and [here](http://stackoverflow.com/a/21319414/1708801) I filed the bug report a year ago. Constant expression extension are [problematic since they can effect sfinae](http://stackoverflow.com/q/27744079/1708801). – Shafik Yaghmour Aug 11 '15 at 13:28
  • @dyp hmmm, but what replaced it would still apply to this case, unless I am missing some subtle in the change in wording. – Shafik Yaghmour Aug 11 '15 at 13:35
  • 1
    Oh, you're right. It's still illegal. Though it's probably overly restrictive. I'm not sure which of the examples in your sample program you'd consider legal. As far as I understand [expr.const], the comparison `&sp1 != &sp2` as well as the two `is_shorter` calls are **not** constant expressions. But both clang++ and g++ accept `is_shorter(s1, s2)` as a constant expression. – dyp Aug 11 '15 at 15:27
  • @dyp fiar point, it is not totally clear, I will update later to fix up the wording. – Shafik Yaghmour Aug 11 '15 at 15:30
  • Maybe we should ask on the people from GCC/CLANG? Looks like the WG21 mailing lists are not for random strangers. – Peter Aug 11 '15 at 15:58
  • 1
    @Peter and cc Shafik: Please see https://groups.google.com/a/isocpp.org/d/topic/std-discussion/ppHUwsgl6yc/discussion – dyp Aug 11 '15 at 20:13
  • @dyp I updated my answer, I removed the examples because they were actually confusing ... I see why you asked so many questions now. I see Richard's response ... that is interesting. – Shafik Yaghmour Aug 11 '15 at 20:23
  • *sigh* I misunterstood the explanition at frist time on the list, it is not a compiler bug it is a ISO "bug"?! – Peter Aug 12 '15 at 14:21
  • @Peter yes it is a bug in the standard – Shafik Yaghmour Aug 12 '15 at 14:32
  • @ShafikYaghmour Time to clean up comments. There are still two points slightly confusing me in your answer: 1) Why are you mentioning *reference constant expressions* specifically, and *address constant expressions* additionally? http://wg21.cmeerw.net/cwg/issue1454 allows initialization of references from temporaries, which are not lvalues hence not reference constant expressions. Similarly, the `&s0` part of `&s0 == &s1` does not need to be an *address constant expression*. 2) What do you think about Richard Smith's response? Your answer says it's a problem of the compiler. – dyp Aug 12 '15 at 14:35
  • @dyp I have not had the chance to properly think about yet :-( ... it is still not clear to me why an address constant expression is not required since `&` yields a pointer and it seems like only pointers that point to objects of static storage duration can be used in constant expression. Richard's answer at face value makes sense and is consistent with other sections in `5.19` or `5.20` depending on which draft. – Shafik Yaghmour Aug 12 '15 at 14:40
  • As far as I understand the different categories of constant expressions in C++11 IS: You start with "Collectively, ... are called constant expressions.", applied to the full-expression. The full-expression `&s0 != &s1` can only be a literal constant expr, because of its type and prvalueness. So we need to establish that it's a core constant expression. [expr.const]p2 doesn't exclude it from being a core constant expression, so it is one. The sub-expressions do not have to be any kind of constant expression. – dyp Aug 12 '15 at 14:58
  • @dyp I see what you are saying, perhaps I mixing up issues but I having a hard time reconciling it with the logic using in [DR 2005](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#2005) specifically *This renders constexpr meaningless, because although the address computation is trivial, it still must be done dynamically.*. – Shafik Yaghmour Aug 12 '15 at 18:22
  • I think the difference between the examples in DR 2005 and here is that we do not store the address in these examples. IIRC, I have once read that CWG tries to *not* require from compilers to perform constant expression computations across statements. This translates to `constexpr auto& r0 = s0; constexpr auto& r1 = s1; constexpr bool b = &r0 != &r1;` vs `constexpr bool c = &s0 != &s1;` Since `constexpr` applies to statements, we check once per statement if what we've got is really constant, and can rely on this in succeeding statements. – dyp Aug 12 '15 at 18:42
  • Within an expression, there's more freedom; such as C++14 constexpr functions not consisting of constant expressions at all (and function invocation substitution removed). In Standardese, there are several mentions of *lifetime began within the expression* in [expr.const], and I think that's the same concept. – dyp Aug 12 '15 at 18:44
2

The address of the string is the address of the stack object and not the underlying pointer that the string is wrapping. The address might be different on each run but the compiler will load the variables into memory the same way each time so the comparison will always yield the same results.

Note: Different compilers might order the variables in a different way so this code is not portable.

Note 2: comparing pointers for equality (==, !=) is defined behavior for pointers of the same type. trying to compare with relational operators (<,>,<=,>=) is unspecified if:

two pointers p and q of the same type point to different objects that are not members of the same object or elements of the same array or to different functions, or if only one of them is null

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Thanks. Before accepting your answer finally a second question. This is not implementation defined behaviour? – Peter Aug 10 '15 at 14:32
  • 1
    If I recall correctly, comparing the addresses to see which is larger is actually *undefined* behavior! As a more practical matter, some compilers will allocate local variables on the stack starting with the largest address and growing smaller rather than the smallest address and growing larger. Of course, its allowed to do whatever it wants (except give them the same address) –  Aug 10 '15 at 14:35
  • Thanks. Thus comaring the pointers only for equality seems to be defined behaviour. Furthermore I saw this link in an answer, which was removed (why?) some minutes ago: http://wg21.cmeerw.net/cwg/issue1195 – Peter Aug 10 '15 at 14:38
  • If tested it with the relative "less than or equal" (<=) and g++ gave me an nice error about it. – Peter Aug 10 '15 at 14:45
  • @Peter I made some edits to my answer. You might want to evaluate if you still want this to be the accepted answer. – NathanOliver Aug 10 '15 at 14:53
  • @Peter I initially read the question incompletely then I realized there were something else going on but I did not have the answer so I left it deleted. I now believe what you are seeing is a form of an extension as I explain in my no undeleted answer. There are a lot of corner cases with constant expressions and it is interesting so long after C++11 we keep seeing new ones. – Shafik Yaghmour Aug 10 '15 at 20:15