43

Why is std::initializer_list<_E>::size not allowable in a static_assert, even though it's declared as a constexpr in my libstdc++ (v. 4.6)?

For example, the following code:

template<class T, int Length>
class Point
{
  public:
    Point(std::initializer_list<T> init)
    {
      static_assert(init.size() == Length, "Wrong number of dimensions");
    }
};

int main()
{
  Point<int, 3> q({1,2,3});

  return 0;
}

gives the following error:

test.C: In constructor ‘Point<T, Length>::Point(std::initializer_list<_Tp>) [with T = int, int Length = 3]’:
test.C:60:26:   instantiated from here
test.C:54:7: error: non-constant condition for static assertion
test.C:54:73:   in constexpr expansion of ‘init.std::initializer_list<_E>::size [with _E = int, std::initializer_list<_E>::size_type = long unsigned int]()’
test.C:54:7: error: ‘init’ is not a constant expression

Note that this works just fine for a trivial example:

class A
{
  public:
    constexpr int size() { return 5; }
};

int main()
{
  A a;
  static_assert(a.size() == 4, "oh no!");

  return 0;
}
5
  • It does look like it should work the way that you want. Commented Mar 26, 2011 at 0:51
  • Yeah, I'm wondering if this is a compiler bug? I don't want to bother the gcc folks if I'm making a mistake here, but looking at the initializer_list header file leads me to believe that there is something wrong here. Commented Mar 26, 2011 at 20:52
  • I understand that size() is declared as constexpr in libstdc++, but it should be noted that the Standard does not require this. So even if you got this to work (e.g. perhaps using Evgeny Panasyuk's approach below), you couldn't rely on this to work with other implementations of the Standard Library. Commented May 25, 2013 at 5:59
  • 2
    Then again, it seems this is changing in C++14, see 18.9/1. Constructor, size(), begin() and end() are all declared as constexpr in the C++14 proposal. Commented May 25, 2013 at 6:40
  • 1
    This still doesn't seem to work with Clang 3.5 and C++14. That's confusing. Commented Dec 15, 2014 at 4:04

5 Answers 5

42

"Initializer lists" are just horrible kludges.

Don't:

#include <initializer_list>

template<typename T>
void Dont(std::initializer_list<T> list) { // Bad!
    static_assert(list.size() == 3, "Exactly three elements are required.");
}

void Test() { Dont({1,2,3}); }

Do:

template<typename T, std::size_t N>
void Do(const T(&list)[N]) { // Good!
    static_assert(N == 3, "Exactly three elements are required.");
}

void Test() { Do({1,2,3}); }
Sign up to request clarification or add additional context in comments.

15 Comments

@Quant: These are Clang bugs. Works in 3.8 onwards.
const T(&)[N] What is this syntax? What do I need to search for to read more about it?
@Ela782: In this context, const T(&)[N] is a type (lvalue reference to array of N elements of type const T). It is being used as an unnamed function parameter. If you want to assign the parameter a name, use const T (&name)[N].
@Ela782 const &T[N] declares an array of references (which are illegal semantically), the parentheses are necessary to declare a reference to the array itself.
Wouldn't it make more sense here to use const T(&list)[3]? With the size hardcoded we wouldn't need the static_assert at all.
|
14

The compiler says that init is the problem, not init.size().

I guess that the constructor could be called from different places with different length initializers.

(To elaborate: You're trying to write a static_assert that depends on the run-time value of the variable init, namely how many elements it has. static_asserts have to be evaluable at the time the function is compiled. Your code is analogous to this trivially invalid example:)

void foo(int i) { static_assert(i == 42, ""); }
int main() { foo(42); }  // but what if there's a caller in another translation unit?

7 Comments

Yup, but that's the point - I would like to throw a static_assertion whenever someone calls the constructor with an ill-sized initializer list. Because the initializer_list is constructed by the compiler (and has no public constructor), and because the size() method is a constexpr, my static_assert should be totally possible.
Why not? Imagine that I had a templatted constructor that accepted an argument of type T2. It would be perfectly acceptable to put a static_assert to ensure that (for example) std::is_integral<T2>::value resolved to 'true'. This works because std::is_integral<T2>::value is a compile time constant, as is anything labeled constexpr. The reason that std::initializer_list::size() can be labeled constexpr is because it has a special private constructor that only the compiler can access.
Yes, but it doesn't help that 'size()` is constexpr, when init isn't. The compiler complains about init.
Yes, but then why does my simple example work? The object "a" is not constant, yet has a constexpr size() method which works just fine in the static_assert.
Yes, but there is only one "a". There will be a new "init" for each Point object constructed.
|
7

From my discussion with @Evgeny, I realized that this just works (with gcc 4.8 c++11) and may as well do the size check by only accepting a compatible size in the initializer list (in main).

(code link: http://coliru.stacked-crooked.com/a/746e0ae99c518cd6)

#include<array>
template<class T, int Length>
class Point
{
  public:
    Point(std::array<T, Length> init)
    {
//not needed//      static_assert(init.size() == Length, "Wrong number of dimensions");
    }
};

int main()
{
  Point<int, 3> q({1,2,3}); //ok
//  Point<int, 3> q2({1,2,3,4}); //compile error (good!)
  Point<int, 3> q2({1,2}); // ok, compiles, same as {1,2,0}, feature or bug?
  return 0;
}

1 Comment

4

Use following syntax:

LIVE DEMO

#include <initializer_list>

template<class T, int Length>
class Point
{
    std::initializer_list<T> data;
public:
    constexpr Point(std::initializer_list<T> init)
        : data
        (
            init.size() == Length ?
            init : throw 0
        )
    {}
};

int main()
{
    constexpr Point<int, 3> a{{1,2,3}};
    constexpr Point<int, 2> b{{1,2,3}}; // compile time error
}

Refer following SO.


EDIT: Interesting that works on GCC 4.8.1, but does not work on Clang 3.4. Maybe this is related to constexpr of .size() (afaik, in C++14 it is constexpr).

8 Comments

Interesting, can this condition be applied to a non constexpr function but to a constexpr argument (if that thing existed at all). For example void fun(double param, [constexpr] std::initializer_list<double> init){init.size()!=2?throw 0:other...code;}.
@alfC Looks like yes - but parameter should be taken by value, const& does not work. (you don't need throw in such case, static_assert is enough. throw was used to overcome constexpr limitations). But looks like this works only for constexpr methods which do not depend on members
Well, I think I am trying to do something different, which it looks possible in principle, but I cannot do it. See the example: coliru.stacked-crooked.com/a/9f4a561a5fd9178a I want to check at compile time if a constexpr initializer_list has the right size.
@alfC I am not sure, perhaps initializer list really should return something like std::array<T, N> instead of std::initializer_list<T>.
"initializer_list<T, N>" was discussed here stackoverflow.com/questions/7108425/… . And yes, you might be correct that an array<T, 2> could be the whole answer I was looking for because this works: coliru.stacked-crooked.com/a/8fe9af280f3baffc. I think I gave up with this in C++98 (and boost::array) because it didn't work in the pass, but now it works!!. (Now the remaining thing is to wait for initializer_tuple in C++2x.)
|
0

I haven't really figured out what's going on here.

If I say

const std::initializer_list<double> li = { 1, 2.5, 3.7, 4.3 };
static_assert(li.size() == 4, "fail");

I get a complain that 'li' was not declared 'constexper'.

But if I say

constexpr std::size_t dSize(std::initializer_list<double> di)
{
    return di.size();
}

then

static_assert(dSize({1, 2.5, 3.7, 4.3}) == 4, "failed");   // works

but

static_assert(dSize(li) == 4, "failed");

fails with "error: the value of 'li' is not usable in a constant expression"

This is all with -std=c++11

So, somehow, an initializer list passed into an arg list can be part of a constant expression, but an initializer list just declared as a const variable can't.

3 Comments

constexpr (function dSize) doesn't mean it will return a constexpr. It will "try" to do so if certain conditions are met (probably never with std::initializer_list it seems) that is why the last static_assert doesn't work. constexpr means different things when applied to variables and when applied to functions.
I figured out what I was doing wrong. When I declared the 'constexpr' init list, I was doing it within 'main' so it was an automatic variable. When I moved the declaration outside of any function, so that it was a global or static variable, then the 'constexpr' worked correctly and the static asserts worked.
that is interesting actually. Although a global initializer_list wouldn't be very useful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.