Prev: best way to disambiguate an overloaded function.
Next: conversion from `std::_List_iterator<int>' to non-scalar type `std::_List_iterator<int*>' requested
From: Rodolfo Lima on 29 Jun 2010 05:24 Hi, as seen in the last C++0x standard draft, initializer_list's begin/ end/size aren't constexpr anymore. What is the rationale for that? The returned values are known at compile time, I don't see why they cannot be constexpr. Regards, Rodolfo Lima -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Daniel Krügler on 30 Jun 2010 23:49 On 29 Jun., 22:24, Rodolfo Lima <rodo...(a)rodsoft.org> wrote: > Hi, as seen in the last C++0x standard draft, initializer_list's > begin/end/size aren't constexpr anymore. What is the rationale for > that? The returned values are known at compile time, I don't see > why they cannot be constexpr. First, "anymore" implies that at some point of time the standard draft did contain these functions with a constexpr specifier - but this never had been the case, because the first accepted proposal paper http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2672.htm added to http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2691.pdf did not suggest them. Nevertheless, it is correct, that the first initializer list papers (e.g. n1919, n2100, ...) did consider such a specification, but those papers also mentioned some problems with this idea. They came to the conclusion that if we consider std::initializer_list objects as intrinsic types this could somehow be realized via a special rule solely valid for this intrinsic. As of the current definition of constexpr functions and as of the current specification of the initialization of a std::initializer_list object, this could *not* be handled by the "normal" language rules. Additionally it would lead to some severe constraints when the functions could be used as constexpr functions. To explain why, lets consider a possible std::initializer_list implementation that attempts to do the best to realize that. The definition we could try could be as follows (ignoring typedefs, includes, etc.): namespace std { template<class E> class initializer_list { const E* beg; size_t sz; public: constexpr initializer_list(const E* beg, size_t sz) : beg(beg), sz(sz) {} constexpr size_t size() { return sz; } [..] }; } Now this would make the following program well-formed: #include <initializer_list> constexpr std::initializer_list<int> li = {0, 42}; constexpr int sz = li.size(); int main() {} but this slightly modified program wouldn't be: #include <initializer_list> int main() { constexpr std::initializer_list<int> li = {0, 42}; constexpr int sz = li.size(); } The reason is, that in the second example the current specification of the initialization of a std::initializer_list object (see 8.5.4 [dcl.init.list]/4 in the FCD) meets a constraint of constant expressions (see 5.19 [expr.const]/2): "A conditional-expression is a constant expression unless it involves one of the following as a potentially evaluated subexpression [..] � an array-to-pointer conversion (4.2) that is applied to a glvalue that does not designate an object with static storage duration; [..] " This constraint is not satisfied for the second example and thus the initialization of the local li object would be ill-formed (even though the size of that array could still be evaluated as a constant expression). This means that by normal FCD language rules, users could only benefit from constexpr functions of std::initializer_list, if this is an object of static storage duration - I consider this as a very rare case. As indicated above, it would still be possible to get away language restrictions for an compiler- intrinsic type as std::initializer_list and just *require* that size() is supposed to be a constexpr function. This is probably possible, but at this point in time a) it would enforce *all* implementations to realize this (This is not as simple as it looks - even the decimal type specification as of http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2849.pdf does not require decimals to be represented as compiler-intrinsic types). b) it is currently unknown whether the effort required for this specification is justified. It is probably wiser to wait and to see how implementations can handle this over the first time and to possibly consider a stronger requirement in the future. HTH & Greetings from Bremen, Daniel Kr�gler -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Rodolfo Lima on 2 Jul 2010 06:09 Hi Daniel, first of all, thanks for your throughout explanation. On 1 jul, 11:49, Daniel Kr�gler <daniel.krueg...(a)googlemail.com> > First, "anymore" implies that at some point of time the > standard draft did contain these functions with a > constexpr specifier - but this never had been the case, > because the first accepted proposal paper Sure, my bad. > As of the current definition of constexpr functions > and as of the current specification of the initialization > of a std::initializer_list object, this could *not* > be handled by the "normal" language rules. Additionally > it would lead to some severe constraints when the > functions could be used as constexpr functions. To I'm aware of the difficulties involved, but didn't know about the constraints you've mentioned. They would indeed make constexpr initializer_list<E>::size next to useless. I wonder (maybe na�vely), if initializer_list could be something like this: template <class E, size_t N=numeric_limits<size_t>::max()> class initializer_list { const E *m_array; public: initializer_list(const E *a) : m_array(a) {} const E *begin() const { return m_array; } const E *end() const { return begin() + size(); } constexpr size_t size() const { return N; } }; template <class E> class initializer_list<E, numeric_limits<size_t>::max()> { const E *m_array; size_t m_size; public: initializer_list(const E *a, size_t s) : m_array(a), m_size(s) {} const E *begin() const { return m_array; } const E *end() const { return begin() + size(); } size_t size() const { return m_size; } }; So, whenever the compiler must convert a list into a initializer_list<E>, it would do as it currently does, according to FCD. But, to convert to a initializer_list<E,N>, the list must have exactly N items (or if N is a template argument to be deduced, N would be deduced to the number of list items). An valid use case is the following: suppose I'm creating a vector/ matrix class whose dimensions are known at compile time. So I would have (except for any typos, includes, ...): //---------------------------------------- using namespace std; template <class T, int N> class Vector { public: Vector(T v) { fill(m_data, m_data+N, v); } Vector(const initializer_list<T,N> &cols) { copy(cols.begin(), cols.end(), m_data); } T &operator[](int j) { return m_data[j]; } const T &operator[](int j) const { return m_data[j]; } private: T m_data[N]; }; template <class T, int M, int N> class Matrix { public: Matrix(T v) { for(int i=0; i<M; ++i) for(int j=0; j<N; ++j) m_data[i][j] = i==j ? v : 0; } Matrix(const initializer_list<Vector<T,N>, M> &rows) { copy(rows.begin(), rows.end(), m_data); } Vector<T,N> &operator[](int i) { return m_data[i]; } const Vector<T,N> &operator[](int i) const { return m_data[i]; } private: Vector<T,N> m_data[M]; }; template <class T, int M, int N> auto create_matrix(const std::initializer_list<Vector<T,N>,M> &rows) -> Matrix<T,M,N> { return Matrix<T,M,N>(rows); } Matrix<int,2,2> m1{{1,2},{3,4}}; auto m2 = create_matrix({1,2},{3,4}); assert(m1 == m2); // supposing there is a Matrix::operator== //---------------------------------------- For vectors I can create a variadic template constructor and statically assert its size to be equal to N, the call to Vector<T, 1>{1} would end up calling the "wrong" constructor (the first), but with the same semantics of the second. But I can't do the same with Matrix, since the variadic parameters would be deduced to be std::initializer_list, which is invalid according to FCD's 14.9.2.5 [temp.deduct.type]/5 paragraph, so I have to resort to runtime size checking, which is sad. Regards, Rodolfo Lima. -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Daniel Krügler on 2 Jul 2010 09:33 On 2 Jul., 23:09, Rodolfo Lima <rodo...(a)rodsoft.org> wrote: > I wonder (maybe na vely), if initializer_list could be something like > this: > > template <class E, size_t N=numeric_limits<size_t>::max()> > class initializer_list > { > const E *m_array; > public: > initializer_list(const E *a) : m_array(a) {} > > const E *begin() const { return m_array; } > const E *end() const { return begin() + size(); } > constexpr size_t size() const { return N; } > }; > So, whenever the compiler must convert a list into a > initializer_list<E>, it would do as it currently does, according to > FCD. But, to convert to a initializer_list<E,N>, the list must have > exactly N items (or if N is a template argument to be deduced, N would > be deduced to the number of list items). <nod>, this would indeed work, but it was one of the main motivations to make initializer_list *only* dependent on the type, not on the size. This was considered important to prevent different overloads for different sizes. In fact, there would otherwise not much of a win compared to std::array or overloads that accepts references to arrays as in template<class T, size_t N> void f(T (&)[N]); > An valid use case is the following: suppose I'm creating a vector/ > matrix class whose dimensions are known at compile time. So I would > have (except for any typos, includes, ...): [..] > For vectors I can create a variadic template constructor and > statically assert its size to be equal to N, the call to Vector<T, > 1>{1} would end up calling the "wrong" constructor (the first), but > with the same semantics of the second. As of the current wording, no constructor different from an initializer_list constructor will be considered for Vector<T, 1>{1}. Hopefully an NB comment has been submitted to fix that problem. > But I can't do the same with Matrix, since the variadic parameters > would be deduced to be std::initializer_list, which is invalid > according to FCD's 14.9.2.5 [temp.deduct.type]/5 paragraph, so I have > to resort to runtime size checking, which is sad. I agree that we really want to realize constexpr size() function for initializer_list. Maybe a special rule for initializer_list is adequate, the question is when. Greetings from Bremen, Daniel Kr�gler -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ]
From: Rodolfo Lima on 3 Jul 2010 07:50
On 2 jul, 21:33, Daniel Kr�gler <daniel.krueg...(a)googlemail.com> wrote: > <nod>, this would indeed work, but it was one of the > main motivations to make initializer_list *only* > dependent on the type, not on the size. This was considered > important to prevent different overloads for different sizes. > In fact, there would otherwise not much of a win compared > to std::array or overloads that accepts references to > arrays as in In my proposal, in cases where the list's size isn't needed at compile time, the partial specialization would be used, making initializer_list not dependent on list's size (would be the case for std::vector, for instance). Only when I *want* different instantiations for different sizes the base template (dependent on list's size) would be used. The way I see it is a win-win situation, isn't it? Regards, rod -- [ See http://www.gotw.ca/resources/clcm.htm for info about ] [ comp.lang.c++.moderated. First time posters: Do this! ] |