static_assert для initializer_list::size()

Почему std::initializer_list<_E>::size не допускается в static_assert, хотя он объявлен как constexpr в моей libstdc++ (v. 4.6)?

Например, следующий код:

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;
}

дает следующую ошибку:

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

Обратите внимание, что это прекрасно работает для тривиального примера:

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

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

  return 0;
}

person rcv    schedule 25.03.2011    source источник
comment
Похоже, это должно работать так, как вы хотите.   -  person Zan Lynx    schedule 26.03.2011
comment
Да, мне интересно, это ошибка компилятора? Я не хочу беспокоить разработчиков gcc, если я здесь ошибаюсь, но просмотр заголовочного файла initializer_list приводит меня к мысли, что здесь что-то не так.   -  person rcv    schedule 26.03.2011
comment
Я понимаю, что size() объявлено как constexpr в libstdc++, но следует отметить, что Стандарт этого не требует. Таким образом, даже если вы заставили это работать (например, используя подход Евгения Панасюка ниже), вы не могли полагаться на это для работы с другими реализациями стандартной библиотеки.   -  person jogojapan    schedule 25.05.2013
comment
Опять же, кажется, что это меняется в C++14, см. 18.9/1. . Конструктор, size(), begin() и end() объявлены как constexpr в предложении C++14.   -  person jogojapan    schedule 25.05.2013
comment
Это все еще не работает с Clang 3.5 и С++ 14. Это сбивает с толку.   -  person ChristopherC    schedule 15.12.2014


Ответы (5)


Компилятор говорит, что проблема в init, а не в init.size().

Я предполагаю, что конструктор может быть вызван из разных мест с инициализаторами разной длины.

(Для уточнения: вы пытаетесь написать static_assert, которая зависит от значения времени выполнения переменной init, а именно от того, сколько элементов она имеет. static_assert должны быть вычислимы в момент выполнения функции. скомпилирован Ваш код аналогичен этому тривиально недопустимому примеру :)

void foo(int i) { static_assert(i == 42, ""); }
int main() { foo(42); }  // but what if there's a caller in another translation unit?
person Bo Persson    schedule 25.03.2011
comment
Да, но в том-то и дело, что я хотел бы вызывать static_assertion всякий раз, когда кто-то вызывает конструктор со списком инициализаторов неправильного размера. Поскольку initializer_list создается компилятором (и не имеет общедоступного конструктора) и поскольку метод size() является constexpr, мой static_assert вполне возможен. - person rcv; 26.03.2011
comment
Но конструктор только один, поэтому он не может утверждаться в одних местах, а в других нет. static_assert, являющийся временем компиляции, не может работать по-разному для разных вызовов конструктора. - person Bo Persson; 26.03.2011
comment
Почему нет? Представьте, что у меня есть шаблонный конструктор, который принимает аргумент типа T2. Было бы вполне приемлемо поместить static_assert, чтобы гарантировать, что (например) std::is_integral‹T2›::value разрешено как «истина». Это работает, потому что std::is_integral‹T2›::value — это константа времени компиляции, как и все, что помечено как constexpr. Причина, по которой std::initializer_list::size() может быть помечена как constexpr, заключается в том, что у него есть специальный закрытый конструктор, доступ к которому есть только у компилятора. - person rcv; 26.03.2011
comment
Да, но не помогает то, что 'size()' является constexpr, а init - нет. Компилятор жалуется на init. - person Bo Persson; 27.03.2011
comment
Да, но тогда почему мой простой пример работает? Объект a не является константой, но имеет метод constexpr size(), который отлично работает в static_assert. - person rcv; 28.03.2011
comment
Да, но есть только один а. Будет новая инициализация для каждого построенного объекта Point. - person Bo Persson; 28.03.2011
comment
@Boatzart: Проблема в том, что компилятор создает отдельные конструкторы для каждого T2, во время компиляции все разные возможности уже есть. Но вы не знаете, передаете ли вы initializer_list размера 5 или одного размера 3 во время компиляции. И статическое утверждение выполняется во время компиляции, поэтому компилятору необходимо знать все возможные варианты. - person Xeo; 29.03.2011

«Списки инициализаторов» — просто ужасные кладжи.

Не делайте:

#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}); }
person alecov    schedule 31.07.2016
comment
Кажется, это работает на gcc, но не на clang 3.6 или 3.7. - person Quant; 15.12.2016
comment
@Quant: это ошибки Clang. Работает в 3.8 и выше. - person alecov; 15.12.2016
comment
Спасибо. У вас есть ссылка, показывающая, что это ошибка? - person Quant; 16.12.2016
comment
@Quant: Помимо того факта, что это действующий C ++ (и принятый основными компиляторами), у меня нет ссылки на ошибку, которую можно было бы процитировать. Однако я помню, что видел подобные ошибки в Clang, которые могли быть или не быть связаны с этим. - person alecov; 29.12.2016
comment
const T(&)[N] Что это за синтаксис? Что мне нужно найти, чтобы узнать больше об этом? - person Ela782; 25.07.2018
comment
Должно быть const T(&list)[N] - person Fan; 26.07.2018
comment
@ Ela782: В этом контексте const T(&)[N] — это тип (ссылка lvalue на массив из N элементов типа const T). Он используется как безымянный параметр функции. Если вы хотите присвоить параметру имя, используйте const T (&name)[N]. - person alecov; 26.07.2018
comment
@alexcov Хм, а почему бы и не const &T[N] тогда? Это неправильный синтаксис, но концептуально это именно то, чем является const T(&)[N]? - person Ela782; 26.07.2018
comment
@ Ela782 const &T[N] объявляет массив ссылок (которые недопустимы семантически), круглые скобки необходимы для объявления ссылки на сам массив. - person Jake Cobb; 08.09.2018
comment
Не лучше ли здесь использовать const T(&list)[3]? С жестко заданным размером нам вообще не понадобится static_assert. - person Rickard; 31.07.2019
comment
@Rickard: Да, это тоже работает. static_assert по-прежнему полезен, если вы хотите, чтобы в вашем журнале ошибок было собственное сообщение. - person alecov; 31.07.2019
comment
Но вопрос был не в том, хорош или плох initializer_list (IMO не так уж плох), а в том, почему он не работает в этом контексте с constexpr - person Triskeldeian; 05.04.2020
comment
Что заставляет задуматься, а для чего все-таки std::initializer_list? Я даже не думаю, что можно иметь динамический initializer_list, размер которого не может быть известен во время компиляции. Кажется, единственная причина - избежать раздувания кода (нет шаблона N). На самом деле это выглядит как полузатертый тип, стертый (размер-стертый) общего типа T&[N]. Возможно, есть какое-то дополнительное правило преобразования для отдельных элементов (но это можно сделать внутри функции). - person alfC; 06.11.2020
comment
@Rickard, проблема с const T(&list)[3] в том, что (большинство компиляторов?) примут аргумент с более чем 3 элементами. И может предупреждать, только если вам повезет с параметрами предупреждения. Это может быть то, что вы хотите, но с const T(&list)[N] у вас есть полный контроль над тем, что делать, ошибаться, игнорировать или даже предупреждать с помощью прагмы. - person alfC; 06.11.2020
comment
Обратите внимание, что он также передает void Test() { Do({1,2}); } - person hojin; 23.07.2021

Из моего обсуждения с @Evgeny я понял, что это просто работает (с gcc 4.8 c++11) и может также выполнять проверку размера, только принимая совместимый размер в списке инициализаторов (в main).

(кодовая ссылка: 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;
}
person alfC    schedule 12.11.2013
comment
связанные: stackoverflow.com/questions/32999822/ - person alfC; 12.10.2015

Используйте следующий синтаксис:

РЕАЛЬНАЯ ДЕМО

#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
}

Обратитесь к после SO.


РЕДАКТИРОВАТЬ: интересно, что работает на GCC 4.8.1, но не работает на Clang 3.4. Возможно, это связано с constexpr из .size() (афаик, в С++ 14 это constexpr).

person Evgeny Panasyuk    schedule 08.04.2013
comment
Интересно, можно ли это условие применить не к constexpr функции, а к constexpr аргументу (если такая штука вообще существовала). Например void fun(double param, [constexpr] std::initializer_list<double> init){init.size()!=2?throw 0:other...code;}. - person alfC; 11.11.2013
comment
@alfC Похоже на yes - но параметр нужно брать по значению, const& не работает. (в таком случае throw не нужно, достаточно static_assert. throw использовался для преодоления constexpr ограничений). Но похоже, что это работает только для constexpr методов, которые не зависят от элементов - person Evgeny Panasyuk; 11.11.2013
comment
Ну, я думаю, что пытаюсь сделать что-то другое, что в принципе кажется возможным, но я не могу этого сделать. См. пример: coliru.stacked-crooked.com/a/9f4a561a5fd9178a Я хочу проверьте во время компиляции, имеет ли constexpr initializer_list правильный размер. - person alfC; 12.11.2013
comment
Я думаю, что это более полный пример того, что я хочу, coliru.stacked-crooked.com/a /e3fee72a1b5d6181, но кажется невозможным, потому что ссылка на элементы initializer_list в любом случае не является constexpr. Таким образом, я хочу иметь возможность вызывать такие функции, как эта fun(runtime_var1, {1.,2.}), но если вторым аргументом является constexpr initializer_list, то проверьте во время компиляции, что количество элементов равно двум. Я думаю, что это невозможно сделать, если constexpr не может быть указан для каждого аргумента отдельно. - person alfC; 12.11.2013
comment
@alfC Я понимаю, что вы имеете в виду, вот еще один пример. Похоже, что в C++14 begin и end из initializer_list являются constexpr. Аргумент constexpr может решить и эту проблему. - person Evgeny Panasyuk; 12.11.2013
comment
да, я не знаю, я не могу заставить его делать что-то полезное, даже с С++ 14 я не очень оптимистичен, так как кажется, что вызов функции не сможет смешивать аргументы constexpr и non-constexpr, и это то, что я хочу. Конечная причина заключается в том, что я хотел бы, чтобы С++ принимал иерархическую передачу аргументов (вложенной скобкой, например, sum(&function, {0, 10}), даже то, что я бы назвал initializer_tuple (см. здесь stackoverflow.com/q/16703838/225186). Я не вижу внутренних ограничений языка для этих расширенных синтаксисов. - person alfC; 12.11.2013
comment
@alfC Я не уверен, возможно, список инициализаторов действительно должен возвращать что-то вроде std::array<T, N> вместо std::initializer_list<T>. - person Evgeny Panasyuk; 12.11.2013
comment
initializer_list<T, N> обсуждалось здесь stackoverflow.com/questions/7108425/ . И да, вы можете быть правы, что array<T, 2> может быть полным ответом, который я искал, потому что это работает: coliru.stacked-crooked.com/a/8fe9af280f3baffc. Я думаю, что отказался от этого в C++98 (и boost::array), потому что это не работало на проходе, но теперь это работает!! (Теперь осталось дождаться initializer_tuple в C++2x.) - person alfC; 12.11.2013

Я действительно не понял, что здесь происходит.

Если я скажу

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

Я получаю жалобу на то, что «li» не был объявлен «constexper».

Но если я скажу

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

тогда

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

но

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

завершается с ошибкой: значение «li» нельзя использовать в постоянном выражении.

Это все с -std=c++11

Таким образом, каким-то образом список инициализаторов, переданный в список аргументов, может быть частью константного выражения, но список инициализаторов, только что объявленный как константная переменная, не может.

person Bill Chapman    schedule 15.10.2020
comment
constexpr (функция dSize) не означает, что она вернет constexpr. Он попытается сделать это, если будут выполнены определенные условия (вероятно, никогда с std::initializer_list, кажется), поэтому последний static_assert не работает. constexpr означает разные вещи при применении к переменным и к функциям. - person alfC; 06.11.2020
comment
Я понял, что я делаю неправильно. Когда я объявлял список инициализации constexpr, я делал это внутри main, поэтому это была автоматическая переменная. Когда я переместил объявление за пределы какой-либо функции, чтобы оно было глобальной или статической переменной, то constexpr работал правильно, и статические утверждения работали. - person Bill Chapman; 08.11.2020
comment
это интересно на самом деле. Хотя глобальный initializer_list был бы не очень полезен. - person alfC; 08.11.2020