Dereferencing a pointer to non-constexpr in a constant expression

233 views
Skip to first unread message

j4...@dropbox.com

unread,
Jul 21, 2015, 5:17:05 PM7/21/15
to ISO C++ Standard - Discussion
Is this code well-formed?
    int x = 0;
    constexpr int* ptr = &x;
    static_assert(&*ptr == &x, "");

Current implementations seem to disagree. The issue seems to be whether dereferencing a pointer-to-non-constexpr is legal, in the case where the resulting value is not used, but instead only its address is taken. (By comparison, all recent compilers I've tried are ok with asserting that ptr == &x). This seems to me to be very analogous to one of the cases raised in CWG issue 232 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232), where it was decided that "char *p = 0; char *q = &*p" is well-defined. In that case, even though *p seems to be dereferencing a null pointer, the value is not used, so no UB. Should the same logic apply here?

(I'm not convinced myself, which is why I'm posting here rather than filing a compiler bug...)

Ville Voutilainen

unread,
Jul 21, 2015, 5:22:46 PM7/21/15
to std-dis...@isocpp.org
I think the code you showed before illustrates it a tad better,
because we see that x is global:

int x = 0;
constexpr int* ptr = &x;
static_assert(ptr == &x, ""); // gcc/clang/MSVC all accept this
static_assert(&*ptr == &x, ""); // gcc/clang accept this, but not MSVC
int main(){}

j4...@dropbox.com

unread,
Jul 21, 2015, 5:34:42 PM7/21/15
to ISO C++ Standard - Discussion
And FWIW, this came up as a result of my attempt to port Andrzej Krzemieński's std::experimental::optional implementation to MSVC: https://github.com/akrzemi1/Optional/pull/28

Edward Catmur

unread,
Jul 23, 2015, 4:10:37 AM7/23/15
to ISO C++ Standard - Discussion, j4...@dropbox.com
The relevant checks on &*ptr are as follows:

* Does it perform lvalue-to-rvalue conversion (on an ineligible object)? No, because ptr is constexpr and the lvalue designated by *ptr is not subjected to lvalue-to-rvalue conversion.
* Is it UB? No, the unary * is valid as ptr is a pointer to complete object type and points to an object.

It's definitely MSVC that is incorrect here.

Note also that MSVC is inconsistent in a way, allowing the binding of a constexpr lvalue reference to the result of unary *; it allows us to write:

constexpr int& r = *p;
static_assert(&r == &i, "!!");

I don't know whether this would be of any use to you as a workaround.

Myriachan

unread,
Jul 23, 2015, 4:31:04 PM7/23/15
to ISO C++ Standard - Discussion, j4...@dropbox.com, e...@catmur.co.uk
On Thursday, July 23, 2015 at 1:10:37 AM UTC-7, Edward Catmur wrote:
The relevant checks on &*ptr are as follows:

* Does it perform lvalue-to-rvalue conversion (on an ineligible object)? No, because ptr is constexpr and the lvalue designated by *ptr is not subjected to lvalue-to-rvalue conversion.
* Is it UB? No, the unary * is valid as ptr is a pointer to complete object type and points to an object.

It's definitely MSVC that is incorrect here.

Note also that MSVC is inconsistent in a way, allowing the binding of a constexpr lvalue reference to the result of unary *; it allows us to write:

constexpr int& r = *p;
static_assert(&r == &i, "!!");

I don't know whether this would be of any use to you as a workaround.


Well, this thread led me to find what might be a bug in GCC.  Clang accepts this, but GCC seems to throw a spurious error.  Am I missing something that makes this code below ill-formed?

#include <type_traits>

int x;

int main()
{
   
// OK
   
static_assert(&*&x == &x, "meow");
   
// OK
   
static_assert(std::integral_constant<bool, (&*&x == &x)>::value, "kitty");
   
// GCC errors "template argument 2 is invalid"
   
static_assert(std::integral_constant<bool, &*&x == &x>::value, "purr");
   
return 0;
}


Melissa

Edward Catmur

unread,
Jul 24, 2015, 7:07:25 AM7/24/15
to ISO C++ Standard - Discussion, j4...@dropbox.com, myri...@gmail.com
Excellent! The problem seems to be that gcc gets confused by an unparenthesized address-of expression as the first operand of any operator where that operator-expression is the argument of a non-type template parameter. Some reduced examples:

template<bool B> struct S {};
int x;
constexpr struct Y { constexpr Y() {} bool const B = true; } y;

S
<&y ->* (&Y::B)> a; // gcc: error: template argument 1 is invalid
S
<&x == nullptr> b;  // same
S
<&x && false> c;    // same
S
<&x ? false : true> d; // same

I'm guessing there must be something odd in gcc's handling of the address-of operator as I can't provoke it with any other unary operator (note that this can also be provoked using the "bitand" spelling). It happens with every binary and ternary operator that has lower precedence than "&" and can appear inside <> without parentheses.

I managed to break clang as well, with the following monstrosity:

template<bool B> struct S {};
struct Y {
 
constexpr Y() {}
 
constexpr bool operator=(int) const { return false; }
};
struct W {
 
constexpr W() {}
 
constexpr Y operator&() const { return {}; }
};
constexpr W w;
S
<&w = 0> a; // clang: error: expected '>'

gcc compiles this only if "&w" or the whole expression "&w = 0" is parenthesized. MSVC heroically compiles it and even gets the behavior correct!

Edward Catmur

unread,
Jul 24, 2015, 9:21:05 AM7/24/15
to ISO C++ Standard - Discussion, j4...@dropbox.com, myri...@gmail.com, e...@catmur.co.uk
Quick update: clang doesn't like any assignment or augmented assignment expression as the argument of a non-type template parameter:

template<bool B> struct S {};
struct Y {
 
constexpr Y() {}

 
constexpr Y const& operator+=(int) const { return *this; }
 
constexpr operator bool() const { return true; }
};
S
<Y{} += 1> s; // error: expected '>'

However, it will accept the expression if parenthesized. Oddly enough, it doesn't have a problem with prefix or suffix increment or decrement operators.

Edward Catmur

unread,
Jul 24, 2015, 1:14:49 PM7/24/15
to ISO C++ Standard - Discussion, j4...@dropbox.com, myri...@gmail.com, e...@catmur.co.uk
Ah, my bad: an assignment-expression is not a converted constant expression, so clang is perfectly correct:

constant-expression:
    conditional-expression

BTW, I think gcc is getting hung up on the requirement up to C++14 that a pointer template argument must be of the form & id-expression. Of course, this changes entirely in C++1z where anything goes (e.g. true ? &x : nullptr is allowed in C++1z).

Richard Smith

unread,
Jul 24, 2015, 2:25:14 PM7/24/15
to std-dis...@isocpp.org, j4...@dropbox.com, Myriachan, e...@catmur.co.uk
On Fri, Jul 24, 2015 at 10:14 AM, Edward Catmur <e...@catmur.co.uk> wrote:
Ah, my bad: an assignment-expression is not a converted constant expression, so clang is perfectly correct:

In your defence, Clang's diagnostic is not very good here. I've filed llvm.org/PR24250 for you.
 

--

---
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/.

Edward Catmur

unread,
Jul 27, 2015, 4:56:25 PM7/27/15
to ISO C++ Standard - Discussion, j4...@dropbox.com, myri...@gmail.com, e...@catmur.co.uk
On Friday, 24 July 2015 12:07:25 UTC+1, Edward Catmur wrote:
Excellent! The problem seems to be that gcc gets confused by an unparenthesized address-of expression as the first operand of any operator where that operator-expression is the argument of a non-type template parameter. Some reduced examples:

template<bool B> struct S {};
int x;
constexpr struct Y { constexpr Y() {} bool const B = true; } y;

S
<&y ->* (&Y::B)> a; // gcc: error: template argument 1 is invalid
S
<&x == nullptr> b;  // same
S
<&x && false> c;    // same
S
<&x ? false : true> d; // same

I'm guessing there must be something odd in gcc's handling of the address-of operator as I can't provoke it with any other unary operator (note that this can also be provoked using the "bitand" spelling). It happens with every binary and ternary operator that has lower precedence than "&" and can appear inside <> without parentheses.

Reply all
Reply to author
Forward
0 new messages