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 и C++14. Това е объркващо.   -  person ChristopherC    schedule 15.12.2014


Отговори (5)


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

Предполагам, че конструкторът може да бъде извикан от различни места с инициализатори с различна дължина.

(За уточнение: Опитвате се да напишете static_assert, което зависи от стойността на променливата init по време на изпълнение, а именно от това колко елемента има. static_asserts трябва да могат да се изчислят по времето, когато функцията е компилиран. Вашият код е аналогичен на този тривиално невалиден пример:)

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 е разрешено на „true“. Това работи, защото 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
Да, но има само едно а. Ще има нов init за всеки конструиран 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() (afaik, в C++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 Изглежда като да - но параметърът трябва да се приема по стойност, 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
да, не знам, не мога да го накарам да прави нищо полезно, дори и с C++14 не съм много оптимистично настроен, тъй като изглежда, че извикването на функция няма да може да смесва аргументи constexpr и не-constexpr, и това е, което искам. Основната причина е, че бих искал C++ да приема йерархично предаване на аргументи (чрез вложена скоба, напр. 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“ не е обявено за „constestper“.

Но ако кажа

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
Разбрах какво правя грешно. Когато декларирах init списъка 'constexpr', го правех в 'main', така че беше автоматична променлива. Когато преместих декларацията извън която и да е функция, така че да е глобална или статична променлива, тогава 'constexpr' работи правилно и статичните твърдения работят. - person Bill Chapman; 08.11.2020
comment
това всъщност е интересно. Въпреки че глобален initializer_list не би бил много полезен. - person alfC; 08.11.2020