What's the point of constexpr end istream (sentinel) iterators?
Asked Answered
M

1

10

N2976 suggested adding constexpr to some spots in the standard library. It notes that iostreams are inappropriate for constexpr EXCEPT end iterators. So istream_iterator and istreambuf_iterator were given constexpr default constructors and that's about it. For example, you can see in the libstdc++ implementation that constexpr only appears once in the entire file. The LWG that sparked this change was #1129. It says:

istream_iterator and istreambuf_iterator should support literal sentinel values. The default constructor is frequently used to terminate ranges, and could easily be a literal value for istreambuf_iterator, and istream_iterator when iterating value types. [Rest omitted]

This doesn't make a whole lot of sense to me. Can someone show me an example of what they mean?

N3308 is another paper that mentions but doesn't explain the issue:

Some of the istream_iterator<T> constructors are required to be constexpr if T is a literal type. The intention is to allow existing implementation technique of storing a type of T inline to continue to work. [libstdc++ does this, _Tp _M_value] However, it actually rules out this technique: the default and copy constructors of T need not be marked constexpr, and if they are not, the istream_iterator<T> constructors could not be instantiated as constexpr.

The above explains the trivial copy constructor and destructor, but not why the default constructor is marked constexpr.

Furthermore, testing on online GCC 5.2.0, I copied libstdc++'s implementation. The only change is I removed constexpr from istream_iterator(). In both cases, the assemblies are identical.

With constexpr

Without constexpr

Marylynnmarylynne answered 19/9, 2015 at 7:54 Comment(14)
What isn't clear? A default constructed stream iterator is often used as the end iterator, like while (iter != istream_iterator()). Having that as a constexpr might save us a nanosecond or two in the loop.Blain
@BoPersson How is the expression constexpr if only the end iterator can be constexpr? (Also I don't believe "save us a nanosecond or two" would justify a defect, a paper, and then library implementators thinking it's worth development time)Marylynnmarylynne
I suppose a 'moving' iterator needs to make calls to live data so its state can't be determined at compile time. But a terminating iterator can be completely static and entirely deduced at compile time because all it has to do is compare equal with a 'moving' iterator that ran out of data..Ancier
The expression isn't constexpr (and that wouldn't work as a loop condition anyway), but comparing iter to a constant might be cheaper than comparing to something else.Blain
@BoPersson isn't it constexpr to allow constant initialization in a static initialization phase? can non-static-storage-duration temporary local objects be statically initialized?Wilford
@Piotr - 1. Yes. 2. Sometimes. Under the "as-if"-rule, if the compiler can figure out that we don't see the difference, it can do anything. Making a constructor constexpr just might help the compiler with the figure-out-part. In this particular case, I believe most optimizers noticed that istream_iterator() produced a constant, even before it was made constexpr. So it is not an important change in the library.Blain
@BoPersson I copy/pasted libstdc++'s implementation into coliru. Removing constexpr will produce identical assemblies. Again I remain unconvinced that this microoptimization is the true reason.Marylynnmarylynne
@Marylynnmarylynne - I think this is a "Why not?"-change to the library. When constexpr was introduced, someone produced a list of things in the standard library that could be made constexpr. In this case, the change is extremely easy to do, doesn't break any old code, and if it has any effect on performance, at least it will not be negative. So, why not?Blain
@PiotrSkotnicki Making std::mutex constexpr will solve the static initialization (#828) issue, but that's the only issue listed in N2976 that mentions static initialization. I believe the issue is related to making istream_iterator a literal type, like most of the other issues. However I'm having trouble connecting the dots.Marylynnmarylynne
@PiotrSkotnicki If T is a literal type, istream_iterator is a literal type. It'll have a trivial copy constructor and destructor.Marylynnmarylynne
@Marylynnmarylynne mark an instance of an end sentinel object as static or thread_local and see if constexpr makes difference in assembly codeWilford
@PiotrSkotnicki It did, but minorMarylynnmarylynne
@Marylynnmarylynne I got this result, based on this and thatWilford
@PiotrSkotnicki Ah okay. I had istream_iterator constexpr in both cases.Marylynnmarylynne
E
3

An example of an-end of stream iterator being used as a sentinel value is here:

// istream_iterator example
#include <iostream>     // std::cin, std::cout
#include <iterator>     // std::istream_iterator

int main () {
  double value1, value2;
  std::cout << "Please, insert two values: ";

  std::istream_iterator<double> eos;              // end-of-stream iterator
  std::istream_iterator<double> iit (std::cin);   // stdin iterator

  if (iit!=eos) value1=*iit;

  ++iit;
  if (iit!=eos) value2=*iit;

  std::cout << value1 << "*" << value2 << "=" << (value1*value2) << '\n';

  return 0;
}

http://www.cplusplus.com/reference/iterator/istream_iterator/istream_iterator/

Declaring this a constexpr allows the compiler to fold calls that create end-of-stream iterators into constants, rather than calling a function each time. It might otherwise have to do so on each iteration of a loop.

Excitability answered 19/9, 2015 at 8:20 Comment(12)
Sorry, this isn't a convincing argument. Removing constexpr will produce the exact same assembly.Marylynnmarylynne
@Marylynnmarylynne That's 1 particular case of 1 implementation. Having it constexpr would guarantee the same over all compilers and in the general case. And if you're on the task to go over the standard library and figure out what could be made constexpr, it doesn't make sense to omit something, even if has no real benefit (on one compiler) today.Hartmann
@Hartmann shouldn't eos have static or thread storage duration to have a guarantee that initialization is performed at compile time?Wilford
Simple example: while ( iit != istream_iterator<double>() )Excitability
@Hartmann Here's clang with libc++ (I removed some GCC specific stuff, and qualified names with std::). I'd test on MSVC but VS2013 doesn't support constexpr.Marylynnmarylynne
@Marylynnmarylynne Sure, but I'm not sure I understand why it is overly interesting what a particular compiler does today. If you want to compare what some compilers do, I'm sure you could remove a whole lot of plain const too, and get the same assembly. But both const and constexpr communicate to the programmer too, not just to the compiler.Hartmann
@Marylynnmarylynne Also not a particularly convincing example, since the compiler can just inline the istream_iterator code; in other words, it can do its own analysis of the constant-ness of the expression. Much more convincing would be an example comparing a foreign function with and without constexpr.Tentation
@Lorehead "Declaring this a constexpr allows the compiler to fold calls that create end-of-stream iterators into constants" can you provide standard reference that says so (for automatic storage duration objects)?Wilford
@Piotr Slotniki The ones in the OP. That's what "literal sentinel values" means.Excitability
@Lorehead I asked, where did you get this information from, that a compiler can perform certain optimizations on local, non-static, non-threadlocal temporaries only when they have a constexpr constructor?Wilford
@Marylynnmarylynne Is what you're looking for an implementation where removing constexpr prevents the compiler from optimizing, today? I don't have the resources to test for that right now, but it might affect things such as whether the end-of-stream iterator can be a template argument or assigned to a constexpr. And part of the motive might be to prevent implementers from writing versions of the end iterator that aren't constants; that look up something in the current locale, perhaps.Excitability
@Piotr Skotnicki You're right that eos in the example I quoted should not have been declared an automatic, non-const local variable. I'm on my phone right now, but I've noticed that g++ has trouble automatically inlining and folding double return values, so that's what I would test first to look for a real-world example where constexpr helps.Excitability

© 2022 - 2024 — McMap. All rights reserved.