Topic: Processing strings with constexpr functions (Legal?)


Author: Faisal Vali <faisalv@gmail.com>
Date: Sat, 23 May 2009 11:45:26 CST
Raw View
On May 22, 2:48 am, Gabriel Dos Reis <g...@cs.tamu.edu> wrote:
> Faisal Vali <fais...@gmail.com> writes:
>
<snip>
> >> >   constexpr int rlength(const char* d)
> >> >   {
> >> >      return *d ? rlength(d + 1) + 1 : 0;
> >> >   }
<snip>
> | Based on the list (I believe in 5.19) of what does not qualify as a
> | constant expression
> |    - you can access an lvalue as long as it refers to static storage.
> |
> | Can someone please double check my interpretation on this?
>
> And as long as you don't do anything that would require the compiler to
> know the value of its address.

know the value of the address - or the value at the address?

i.e - these are legal, right? (they require the compiler to have some
knowledge of the address or offset of the constexpr variable)
constexpr char global_char = 'a';
constexpr char* pc = &global_char;
constexpr char* const* ppc = &pc;
template<char* PC> struct S { };
S<&global_char> s1; // ok
S<pc> s2; // ok

I also don't see why the compiler would struggle to assess the literal
value stored in the "memory cell" (of an object with static storage
such as literal strings) of constexpr variables.

i.e. this should all be doable at compile time, right?
template<char C> struct R {};
R<global_char> r;
constexpr char cstr[] = "hello, world";
constexpr char cstr_1 = cstr[1];


<snip>
> | I believe pointers can be constexpr's as long as they refer to static
> | storage.
>
> There is a difference between being 'constexpr', and being 'literal
> types' for the purpose of static initialization.  A pointer type is a
> literal type (for the purpose of static initialization.)
>
> The simple rule of thumb is that a function can be constexpr if it is
> sufficiently enough that it can it can be evaluated at compile time when
> called with constant expressions arguments.

Inferring from the above examples (that I assume can be performed at
compile-time), I don't see why calculating the length of a string
literal can not be done at compile time.  It seems that the rlength
function should have all the information it needs to do the
calculation at compile time.



Thanks for all the responses so far :)
regards,
Faisal Vali
Radiation Oncology
Loyola



--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Gabriel Dos Reis <gdr@cs.tamu.edu>
Date: Sun, 24 May 2009 14:49:13 CST
Raw View
Faisal Vali <faisalv@gmail.com> writes:

| On May 22, 2:48 am, Gabriel Dos Reis <g...@cs.tamu.edu> wrote:
>> Faisal Vali <fais...@gmail.com> writes:
>>
| <snip>
>> >> >   constexpr int rlength(const char* d)
>> >> >   {
>> >> >      return *d ? rlength(d + 1) + 1 : 0;
>> >> >   }
| <snip>
>> | Based on the list (I believe in 5.19) of what does not qualify as a
>> | constant expression
>> |    - you can access an lvalue as long as it refers to static storage.
>> |
>> | Can someone please double check my interpretation on this?
>>
>> And as long as you don't do anything that would require the compiler to
>> know the value of its address.
>
| know the value of the address - or the value at the address?
>
| i.e - these are legal, right? (they require the compiler to have some
| knowledge of the address or offset of the constexpr variable)
| constexpr char global_char = 'a';
| constexpr char* pc = &global_char;
| constexpr char* const* ppc = &pc;
| template<char* PC> struct S { };
| S<&global_char> s1; // ok
| S<pc> s2; // ok
>
| I also don't see why the compiler would struggle to assess the literal
| value stored in the "memory cell" (of an object with static storage
| such as literal strings) of constexpr variables.

None of the example above requires the compiler to know the values of the
addresses.  There are guaranteed static initializations.  That does not
imply that the compiler actually knows the values of the addresses being
taken.  Not even

  S<&global_char> s1;

| i.e. this should all be doable at compile time, right?
| template<char C> struct R {};
| R<global_char> r;
| constexpr char cstr[] = "hello, world";
| constexpr char cstr_1 = cstr[1];
>
>
| <snip>
>> | I believe pointers can be constexpr's as long as they refer to static
>> | storage.
>>
>> There is a difference between being 'constexpr', and being 'literal
>> types' for the purpose of static initialization.  A pointer type is a
>> literal type (for the purpose of static initialization.)
>>
>> The simple rule of thumb is that a function can be constexpr if it is
>> sufficiently enough that it can it can be evaluated at compile time when
>> called with constant expressions arguments.
>
| Inferring from the above examples (that I assume can be performed at
| compile-time), I don't see why calculating the length of a string
| literal can not be done at compile time.  It seems that the rlength
| function should have all the information it needs to do the
| calculation at compile time.

This is NOT like any of the above examples (which are all static
initializations).

 # constexpr int rlength(const char* d)
 # {
 #     return *d ? rlength(d + 1) + 1 : 0;
 # }


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: micael.dark@gmail.com
Date: Sun, 24 May 2009 15:13:29 CST
Raw View
On 21 mayo, 18:00, Gabriel Dos Reis <g...@cs.tamu.edu> wrote:
> SG <s.gesem...@gmail.com> writes:
>
> | Also, recursion might not be legal.
>
> Recursion is now valid.
>
> --
> [ comp.std.c++ is moderated.  To submit articles, try just posting with ]
> [ your news-reader.  If that fails, use mailto:std-...@netlab.cs.rpi.edu]
> [              --- Please see the FAQ before posting. ---               ]
> [ FAQ:http://www.comeaucomputing.com/csc/faq.html                     ]

Gabriel, do you think allowing constexpr recursion is really a good
idea? IMHO its potential to be misused is big (as defined in current C+
+0x).

As far as I know, we can't detect at compile-time if a function is
being called using its constexpr mode or not. This means that coding
something like

constexpr int fibonacci(int n)
{
   return ((n < 2)? n : fibonacci(n - 1) + fibonacci(n - 2));
}

is a bad idea since, if evaluated at runtime, will take so much time
(constexpr-triggering-failure includes, among calling the function
with non-constant parameters, calling it from a different translation
unit even if we call it with constant parameters).

A possible but equally bad solution is

constexpr int fibonacci_compiletime(int n)
{
   return ((n < 2)? n : fibonacci(n - 1) + fibonacci(n - 2));
}

int fibonacci_runtime(int n)
{
   int needed[2] = { 0, 1 };

   for (int i = 0; i < n; ++i) {
       needed[i & 1] = needed[0] + needed[1];
   }

   return needed[n & 1];
}

for exactly the same reason. Probably a good solution for fibonacci
would be

constexpr int fibonacci_impl_(int value, int needed, int times)
{
   return ((times == 0)? value : fibonacci_impl(value + needed,
value, times - 1));
}

constexpr int fibonacci(int n)
{
   return ((n < 2)? n : fibonacci_impl_(0, 1, n));
}

but now we need to code every constexpr function in its iterative
equivalent with recursion to prevent this kind of problem. Tail
recursion is easy to treat and easily optimized when compiling the
runtime version of the function, but most other forms of recursion
aren't.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Gabriel Dos Reis <gdr@cs.tamu.edu>
Date: Mon, 25 May 2009 11:13:28 CST
Raw View
micael.dark@gmail.com writes:

| On 21 mayo, 18:00, Gabriel Dos Reis <g...@cs.tamu.edu> wrote:
>> SG <s.gesem...@gmail.com> writes:
>>
>> | Also, recursion might not be legal.
>>
>> Recursion is now valid.
|
| Gabriel, do you think allowing constexpr recursion is really a good
| idea?

Compile-time recursion has a track record of being a controversial topic
in C++ -- e.g. see the genesis of what is now called 'meta programming
techniques in C++'.  As such, the original constexpr proposal was
cautious in saying that recursion should be included only if compelling
use cases are shown (no, I do not view compile factorial or fibonacci as
compelling use cases).  That was not because recursion itself would pose a
fundamental new challenge to implementations -- C++ implementations
know how to deal with that sort of things since templates.  Over the
years, people have shown compelling use cases for system programming
(e.g. bit population counts, etc).  Ultimately, I think we should judge
this based on merits, not in terms of 'good' versus 'bad', especially
when we do not have working definitions of 'good' or 'bad'.

Preventing constexpr functions from being recursive will not prevent
compile time recursion -- people will continue to use template trickeries.

| IMHO its potential to be misused is big (as defined in current C++0x).

Almost any useful language feature has a big potential misuse.

| As far as I know, we can't detect at compile-time if a function is
| being called using its constexpr mode or not.

That is not exactly true.


| This means that coding something like
|
| constexpr int fibonacci(int n)
| {
|    return ((n < 2)? n : fibonacci(n - 1) + fibonacci(n - 2));
| }
|
| is a bad idea since, if evaluated at runtime, will take so much time
| (constexpr-triggering-failure includes, among calling the function
| with non-constant parameters, calling it from a different translation
| unit even if we call it with constant parameters).

Then, don't do it.

[...]

| but now we need to code every constexpr function in its iterative
| equivalent with recursion to prevent this kind of problem. Tail
| recursion is easy to treat and easily optimized when compiling the
| runtime version of the function, but most other forms of recursion
| aren't.

Not every function should be coded as constexpr, just because it can be.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Faisal Vali <faisalv@gmail.com>
Date: Thu, 21 May 2009 12:13:26 CST
Raw View
A warm thanks to all who responded to my earlier post about attempting
to use the parameters of a constexpr function in the context of an
integral context expression (ICE).  I sensed it would be ill-formed,
but wasn't sure why.  It is clearer to me now - (i.e calling a
constexpr function with an integer literal results in an ICE , but
when parsing/compiling a constexpr function, the parameters themselves
are not ICE's)

So the following is well-formed:
constexpr int foo(const int N) { return N + 1; }
template<int N = foo(5)> struct T { };

But the following is ill-formed: constexpr int foo(const int N)
{ return T<N>(), 0; }

Right?

Also, based on my reading of the constexpr sections of the draft, the
following should be legal.  I wonder if I am mistaken here?

template<int LENGTH>
struct carray
{
 char data[LENGTH];
 constexpr carray(const char* data) : data{data} { }
 template<int N>
 constexpr carray<N> tail(const int Beg)
 { return carray<N>(data + Beg);   }

  constexpr int length()
  {
    return rlength(data);
  }
  private:
  constexpr int rlength(const char* d)
  {
     return *d ? rlength(d + 1) + 1 : 0;
  }
};

int main()
{
 carray<100> str1{"hello,world"};  // compile-time
 carray<30> str2 = str1.tail<30>(6); // compile-time
 std::cout << str2.data; // print "world" runtime
}

thanks for your help,
Faisal Vali
Radiation Oncology
Loyola

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: SG <s.gesemann@gmail.com>
Date: Thu, 21 May 2009 14:19:36 CST
Raw View
On 21 Mai, 20:13, Faisal Vali <fais...@gmail.com> wrote:
> But the following is ill-formed:
>   constexpr int foo(const int N)
>   { return T<N>(), 0; }
> Right?

Yes, it's ill-formed.

> Also, based on my reading of the constexpr sections of the draft, the
> following should be legal.  I wonder if I am mistaken here?
>
> template<int LENGTH>
> struct carray
> {
>  char data[LENGTH];
>  constexpr carray(const char* data) : data{data} { }

How can you initialize an array with a pointer?

>  template<int N>
>  constexpr carray<N> tail(const int Beg)
>  { return carray<N>(data + Beg);   }
>
>   constexpr int length()
>   {
>     return rlength(data);
>   }
>   private:
>   constexpr int rlength(const char* d)
>   {
>      return *d ? rlength(d + 1) + 1 : 0;
>   }

I don't think that dereferencing a pointer inside a constexpr function
is legal (I'm not sure). Also, recursion might not be legal.

> };
>
> int main()
> {
>  carray<100> str1{"hello,world"};  // compile-time
>  carray<30> str2 = str1.tail<30>(6); // compile-time
>  std::cout << str2.data; // print "world" runtime
> }

A "const char*" from a string literal might not be a constant
expression. I'm not sure how pointers are handled in this context. In
case they are allowed you may need a pointer to some object with
external linkage (?).

The initializer of str2 is not a constant expression because it
references 'str1' which is neither declared const nor constexpr.

If you want you can make the compiler reject the code in case the
initializers are not constant expressions. You do so by declaring str1
and str2 as constexpr.

Cheers!
SG


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Gabriel Dos Reis <gdr@cs.tamu.edu>
Date: Thu, 21 May 2009 14:20:20 CST
Raw View
Faisal Vali <faisalv@gmail.com> writes:

| A warm thanks to all who responded to my earlier post about attempting
| to use the parameters of a constexpr function in the context of an
| integral context expression (ICE).  I sensed it would be ill-formed,
| but wasn't sure why.  It is clearer to me now - (i.e calling a
| constexpr function with an integer literal results in an ICE , but
| when parsing/compiling a constexpr function, the parameters themselves
| are not ICE's)
|
| So the following is well-formed:
| constexpr int foo(const int N) { return N + 1; }
| template<int N = foo(5)> struct T { };
|
| But the following is ill-formed: constexpr int foo(const int N)
| { return T<N>(), 0; }
|
| Right?

right.
>
| Also, based on my reading of the constexpr sections of the draft, the
| following should be legal.  I wonder if I am mistaken here?
>
| template<int LENGTH>
| struct carray
| {
|  char data[LENGTH];
|  constexpr carray(const char* data) : data{data} { }
                                       ^^^^^^^^^^

I do not know how the above initialization works.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Faisal Vali <faisalv@gmail.com>
Date: Thu, 21 May 2009 17:01:40 CST
Raw View
On May 21, 3:19 pm, SG <s.gesem...@gmail.com> wrote:
> On 21 Mai, 20:13, Faisal Vali <fais...@gmail.com> wrote:
<snip>
> > Also, based on my reading of the constexpr sections of the draft, the
> > following should be legal.  I wonder if I am mistaken here?
>
> > template<int LENGTH>
> > struct carray
> > {
> >  char data[LENGTH];
> >  constexpr carray(const char* data) : data{data} { }
>
> How can you initialize an array with a pointer?
Bah - you're correct - I should just get rid of the constructor.

Then the following might still work:

const carray<100> str1{"hello,world"};

I don't think the following would work though:
constexpr carray<N> tail(const int Beg)
  { return carray<N>{ data + Beg };   }


> >   constexpr int rlength(const char* d)
> >   {
> >      return *d ? rlength(d + 1) + 1 : 0;
> >   }
>
> I don't think that dereferencing a pointer inside a constexpr function
> is legal (I'm not sure). Also, recursion might not be legal.

Based on the list (I believe in 5.19) of what does not qualify as a
constant expression
   - you can access an lvalue as long as it refers to static storage.

Can someone please double check my interpretation on this?


>
> > };
>
> > int main()
> > {
> >  carray<100> str1{"hello,world"};  // compile-time
> >  carray<30> str2 = str1.tail<30>(6); // compile-time
> >  std::cout << str2.data; // print "world" runtime
> > }
>

> A "const char*" from a string literal might not be a constant
> expression. I'm not sure how pointers are handled in this context. In
> case they are allowed you may need a pointer to some object with
> external linkage (?).

I believe pointers can be constexpr's as long as they refer to static
storage.


>
> The initializer of str2 is not a constant expression because it
> references 'str1' which is neither declared const nor constexpr.

Yes, you're correct here, those variable definitions should read:

  constexpr carray<100> str1{"hello,world"};  // compile-time
  constexpr carray<str1.length()> str2{"hello,world"}; // should also
work


I think getting the 'tail' function to work will take some more
thought.


Thanks.
Faisal Vali
Radiation Oncology
Loyola


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: daniel.kruegler@googlemail.com
Date: Thu, 21 May 2009 17:00:30 CST
Raw View
On 21 Mai, 20:13, Faisal Vali <fais...@gmail.com> wrote:
> So the following is well-formed:
> constexpr int foo(const int N) { return N + 1; }
> template<int N = foo(5)> struct T { };
>
> But the following is ill-formed: constexpr int foo(const int N)
> { return T<N>(), 0; }
>
> Right?

Correct.

> Also, based on my reading of the constexpr sections of the draft, the
> following should be legal.  I wonder if I am mistaken here?
>
> template<int LENGTH>
> struct carray
> {
>  char data[LENGTH];
>  constexpr carray(const char* data) : data{data} { }

The initialization of the data member 'data' is ill-formed.
Let's follow the conditions described in [dcl.init]/16. The
first slot that we would normally take is bullet 2, which
leads us to [dcl.init.string]. But the initializer is no string
literal and all further bullets do not apply and thus bullet 5
makes the program ill-formed.

>  template<int N>
>  constexpr carray<N> tail(const int Beg)
>  { return carray<N>(data + Beg);   }

In the main() code below an carray instance of automatic
storage duration is created, therefore the (non-static) member
'data' has automatic storage duration as well. Now [expr.const]
p. 2 b. 5 excludes this expression from being a /constant
expression/.

>   constexpr int length()
>   {
>     return rlength(data);
>   }

Same problem here because of the array-to-pointer
conversion of an object of automatic storage duration
in the code below.

>   private:
>   constexpr int rlength(const char* d)
>   {
>      return *d ? rlength(d + 1) + 1 : 0;
>   }
>
> };
>
> int main()
> {
>  carray<100> str1{"hello,world"};  // compile-time
>  carray<30> str2 = str1.tail<30>(6); // compile-time
>  std::cout << str2.data; // print "world" runtime
>
> }

HTH & Greetings from Bremen,

Daniel Kr   gler


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Gabriel Dos Reis <gdr@cs.tamu.edu>
Date: Thu, 21 May 2009 17:00:51 CST
Raw View
SG <s.gesemann@gmail.com> writes:

[...]

>>   constexpr int rlength(const char* d)
>>   {
>>      return *d ? rlength(d + 1) + 1 : 0;
>>   }
|
| I don't think that dereferencing a pointer inside a constexpr function
| is legal (I'm not sure).

In general no.  The only exception is when you dereference the 'this'
pointer, lexically, as in:

    struct Locus {
      constexpr Locus(int u, int v) : x(u), y(v) { }
      constexpr int getX() { return (*this).x; }
      constexpr int getY() { return (*this).y; }
    private:
      int x;
      int y;
    };

| Also, recursion might not be legal.

Recursion is now valid.


--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]





Author: Gabriel Dos Reis <gdr@cs.tamu.edu>
Date: Fri, 22 May 2009 01:48:24 CST
Raw View
Faisal Vali <faisalv@gmail.com> writes:


[...]

| const carray<100> str1{"hello,world"};
|
| I don't think the following would work though:
| constexpr carray<N> tail(const int Beg)
|   { return carray<N>{ data + Beg };   }

Indeed, it would not.

>> >   constexpr int rlength(const char* d)
>> >   {
>> >      return *d ? rlength(d + 1) + 1 : 0;
>> >   }
>>
>> I don't think that dereferencing a pointer inside a constexpr function
>> is legal (I'm not sure). Also, recursion might not be legal.
>
| Based on the list (I believe in 5.19) of what does not qualify as a
| constant expression
|    - you can access an lvalue as long as it refers to static storage.
|
| Can someone please double check my interpretation on this?

And as long as you don't do anything that would require the compiler to
know the value of its address.

>
>
>>
>> > };
>>
>> > int main()
>> > {
>> >  carray<100> str1{"hello,world"};  // compile-time
>> >  carray<30> str2 = str1.tail<30>(6); // compile-time
>> >  std::cout << str2.data; // print "world" runtime
>> > }
>>
>
>> A "const char*" from a string literal might not be a constant
>> expression. I'm not sure how pointers are handled in this context. In
>> case they are allowed you may need a pointer to some object with
>> external linkage (?).
>
| I believe pointers can be constexpr's as long as they refer to static
| storage.

There is a difference between being 'constexpr', and being 'literal
types' for the purpose of static initialization.  A pointer type is a
literal type (for the purpose of static initialization.)


The simple rule of thumb is that a function can be constexpr if it is
sufficiently enough that it can it can be evaluated at compile time when
called with constant expressions arguments.
I fully realize that that description may not be sufficiently precise
for this audience (comp.std.c++) but I believe that given the inquiries,
it is good enough starting point to get an understanding of
the 'mysterious' constexpr functions.

--
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html                      ]