8

I was toying a little bit with the indices trick to see where I could go to with and came across a strange error... First, the plain not-so-old indices:

template<std::size_t...>
struct indices {};

template<std::size_t N, std::size_t... Indices>
struct make_indices:
    make_indices<N-1, N-1, Indices...>
{};

template<std::size_t... Indices>
struct make_indices<0, Indices...>:
    indices<Indices...>
{};

I created a compile-time array class derived from a std::initializer_list and had it indexable (assume that N3471 is support by your compiler. It will be in the next standard anyway). Here it is:

template<typename T>
struct array:
    public std::initializer_list<T>
{
    constexpr array(std::initializer_list<T> values):
        std::initializer_list<T>(values)
    {}

    constexpr auto operator[](std::size_t n)
        -> T
    {
        return this->begin()[n];
    }
};

So, I tried to create a function that returns a copy of an array after having added 1 to each of its members:

template<typename T, std::size_t... I>
auto constexpr add_one(const array<T>& a, indices<I...>)
    -> const array<T>
{
    return { (a[I]+1)... };
}

And to finish with the code, here is my main:

int main()
{
    constexpr array<int> a = { 1, 2, 3 };
    constexpr auto b = add_one(a, make_indices<a.size()>());

    return 0;
}

I did not think that code would compile anyway, but I am quite surprised by the error message (Here is the ideone code):

In function 'int main()':
error: 'const smath::array<int>{std::initializer_list<int>{((const int*)(& const int [3]{2, 3, 4})), 3u}}' is not a constant expression

So, could someone explain to me what exactly is not constant enough for the compiler in the above code?

EDIT: Follow-ups for that question

Community
  • 1
  • 1
Morwenn
  • 21,684
  • 12
  • 93
  • 152
  • 2
    A *brace-or-equal* initializer is not an expression and can as such never be a constant expression, is what I think is the issue here. – Xeo Apr 15 '13 at 19:35
  • In fact, Clang 3.2 rejects even the initialization of `a` – Andy Prowl Apr 15 '13 at 19:41
  • @AndyProwl I know, Clang does not support N3471, that's why :) – Morwenn Apr 15 '13 at 19:42
  • @Morwenn: Oh, that explains :) – Andy Prowl Apr 15 '13 at 19:42
  • @Xeo Err, would you care to explain a little bit more? I am trying to understand but am quite lost in the details when I try to think about it. – Morwenn Apr 15 '13 at 19:50
  • 2
    @Morwenn: Here, `{ (a[I]+1)... }` is not an expression. It is a language construct used for initialization, but it is not itself an expression (although individual elements of the initializer list are). Since it is not an expression, it cannot be a *constant* expression. This is what I think Xeo meant. – Andy Prowl Apr 15 '13 at 19:55
  • 1
    Exactly what @Andy says. Really, if you want a compile-time array, just use `std::array` with the augmentations, which can be initialized through aggregate-initialization. – Xeo Apr 15 '13 at 20:03
  • @Xeo The fun part was to try to create a compile-time array without having to specify its length and to be able to use it without some heavy template syntax (it's just for fun, I do not really care any further). That part was ok, but compile-time operations still seem a bit too much to handle :p – Morwenn Apr 15 '13 at 20:05
  • @Morwenn If it's just about creating "a compile-time array w/o having to specify its length", then why not use something like `template < typename T, typename... TT > std::array < T, sizeof...(TT)+1 > create_array(T&& p, TT&&... pp); auto my_array = create_array(1,2,3,4,5);` – dyp Apr 15 '13 at 20:20
  • @DyP Well, `auto` does the trick, but the type still contains the size in the end. Moreover, we can't use the curly-brace-initializer syntax anymore unless we put another pair of enclosing braces, which is not really nice (unless N3526 makes its way to the standard and to the compilers). – Morwenn Apr 16 '13 at 15:12
  • @Morwenn Well the type has to include the size - if you do not want to impose a hard-coded size restriction (e.g. fixed-size 1000 element array, filling only the first n elements). The compiler has to know the `sizeof` of the type, and declaration/definition (requiring a complete type) must be before initialisation. Another way would be to use inheritance and bind a temporary to a `const&`, like `array const& myArr = make_array(1,2,3,4,5);`. – dyp Apr 16 '13 at 15:19
  • @DyP Actually, the fun thing with `std::initializer_list` is that it has a `constexpr` constructor. Therefore, it knows its own size at compile-time without it being specified in the type - hence my choice of inheriting from it -. But the underlying array being implementation-defined, I have no idea how it is handled :) – Morwenn Apr 16 '13 at 19:07
  • 2
    @Morwenn Ah now I get it... dirty and I like it :D But there's another problem: `initializer_list` itself is not a literal type (does not have to be, i.e. AFAIK there's no guarantee). `constexpr` objects must be of literal type [dcl.constexpr]/9 and your `array` isn't a literal type because it inherits of a non-literal type. You can also try to define a `constexpr std::initializer_list mylist;` - fails for the same reason. It is also forbidden for `constexpr` ctors to have non-literal-type parameters [dcl.constexpr]/4. – dyp Apr 16 '13 at 19:40
  • @DyP Well, i guess that's okay even if it is not a literal type since N3471 mandates that `std::initializer_list`'s functions should be `constexpr` (adopted for C++14). GCC already implements it, Clang does not. Maybe it's ok as long as literal types are used in the initializer list; well, actually, I'm not sure. – Morwenn Apr 16 '13 at 20:02
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/28331/discussion-between-dyp-and-morwenn) – dyp Apr 16 '13 at 20:04

1 Answers1

1

From: the man himself http://www.stroustrup.com/sac10-constexpr.pdf

Specifically: its return type, and the types of its parameters (if any), are literal types (see x2.2). For concreteness, literal types include bool, int, or double; its body is a compound statement of the form { return expr; } where expr is such that if arbitrary constant expressions of appropriate types are substituted for the parameters in expr, then the resulting expression is a constant expression as defined in introductory paragraph of x2. The expression expr is called a potential constant expression.

Seth Hays
  • 311
  • 2
  • 11