Защо константен указател не може да бъде константен израз?

Следната програма компилира:

template <const int * P>
class Test{};

extern const int var = 42; //extern needed to force external linkage

int main()
{
    Test<&var> test;
}

Този обаче не го прави, което е изненада за мен:

template <const int * P>
class Test{};

extern const int var = 42; //extern needed to force external linkage
extern const int * const ptr = &var; //extern needed to force external linkage
int main()
{
    Test<ptr> test; //FAIL! Expected constant expression.
}

Алтернативен пример:

int main()
{
   const int size = 42;
   int ok[*&size]; //OK

   const int * const pSize = &size;
   int fail[*pSize]; //FAIL
}

Заключих, че указателят просто не може да бъде постоянен израз, независимо дали е константен и инициализиран с постоянен израз.

Въпроси:

  1. Вярно ли е заключението ми?
  2. Ако е така, защо указателят да не може да бъде константен израз? Ако не, защо горните програми не се компилират?
  3. C++0x(C++11, ако искате) променя ли нещо?

Благодаря за всякакви прозрения!


person Armen Tsirunyan    schedule 12.09.2011    source източник
comment
GCC дава следната грешка: „ptr“ не е валиден аргумент на шаблона, защото „ptr“ е променлива, а не адресът на променлива. Това изглежда показва, че аргументите на шаблона на указателя трябва да бъдат действителни резултати от адрес на...?   -  person Kerrek SB    schedule 12.09.2011
comment
@Kerrek SB: Вижте втория ми пример - изглежда, че указателите не могат да бъдат постоянни изрази... някак си... необяснимо   -  person Armen Tsirunyan    schedule 12.09.2011
comment
По някакъв начин обаче грешката на GCC е по-специфична. Не става въпрос само за това, че ptr не е постоянен израз (което всъщност е), а за факта, че е променлива. Нямам представа обаче къде е включено това в стандарта.   -  person Kerrek SB    schedule 12.09.2011
comment
@Kerrek SB: Подозрението ми е, че не е изрично покрито. Това е едно от нещата, които трябваше да изведем :) Както и да е, повече се интересувам от аспекта защо. Имам предвид, че очевидно стойността на указателя е постоянен израз, но самият указател не е. Какви пречки имаше, за да се позволи това?   -  person Armen Tsirunyan    schedule 12.09.2011


Отговори (3)


Това е малко по-сложно. В C++03 и C++11 &var е постоянен израз, ако var е локална статична/статична класа или променлива за обхват на пространството от имена. Това се нарича константен израз на адрес. Инициализирането на статична променлива на указател на обхвата на клас или пространство от имена с този постоянен израз е гарантирано, че ще бъде направено преди да се изпълни какъвто и да е код (фаза на статична инициализация), тъй като това е постоянен израз.

Въпреки това само от C++11 насам, променлива за указател на constexpr, която съхранява адреса &var, също може да се използва като израз на константа на адрес и само от C++11 насам можете да дереферирате израз на константа на адрес (всъщност , можете да дереферирате дори повече - дори локални адреси на елемент от масив, но нека го запазим по темата) и ако се отнася до постоянна интегрална променлива, инициализирана преди дереферирането, или променлива constexpr, вие отново получавате постоянен израз (в зависимост от типа и стойностна категория, видът на константния израз може да варира). Като такъв, следното е валидно C++11:

int const x = 42;
constexpr int const *px = &x;

// both the value of "px" and the value of "*px" are prvalue constant expressions
int array[*px];
int main() { return sizeof(array); }

Ако е така, защо указателят да не може да бъде константен израз? Ако не, защо горните програми не се компилират?

Това е известно ограничение във формулировката на стандарта - в момента той позволява само други параметри на шаблона като аргументи или & object за параметър на шаблон от тип указател. Въпреки че компилаторът трябва да може да прави много повече.

person Johannes Schaub - litb    schedule 12.09.2011
comment
Намеквате ли, че моят ` const int size = 42; int ok[*&size]; //OK` всъщност е неправилно оформено в C++03? - person Armen Tsirunyan; 13.09.2011
comment
@Армен да. GCC приема някои неправилно оформени изрази, защото сгъва някои последователности от оператори рано, които смята за излишни. - person Johannes Schaub - litb; 13.09.2011
comment
Въпреки това GCC отхвърля Test<px> because ‘px’ is a variable, not the address of a variable... това грешка в изпълнението ли е? - person Kerrek SB; 13.09.2011
comment
@KerrekSB не може да възпроизведе това с GCC8.2 и -std=c++17: godbolt.org/z/ Oyfypv - person Johannes Schaub - litb; 29.01.2019

Все още не е разрешено в C++0x. temp.arg.nontype изисква:

Аргумент-шаблон за параметър-шаблон, който не е тип, не е шаблон, трябва да бъде едно от:

  • за шаблонен параметър без тип от интегрален или изброен тип, преобразуван константен израз (5.19) от типа на шаблонния параметър; или
  • името на шаблон-параметър без тип; или
  • постоянен израз (5.19), който обозначава адреса на обект със статична продължителност на съхранение и външна или вътрешна връзка или функция с външна или вътрешна връзка, включително шаблони на функции и идентификатори на шаблони на функции, но с изключение на нестатични членове на класа, изразено (без да се вземат предвид скобите) като & id-expression, с изключение на това, че & може да бъде пропуснато, ако името се отнася до функция или масив и трябва да бъде пропуснато, ако съответният параметър на шаблона е препратка; или
  • постоянен израз, който дава стойност на нулев указател (4.10); или
  • постоянен израз, който се оценява на нулева стойност на указател на член (4.11); или
  • указател към член, изразен, както е описано в 5.3.1.

оригинален отговор:

  1. В C++03 само интегралните изрази могат да бъдат константни изрази.
  2. Защото стандартът го казва (естествено).
  3. В C++0x n3290 включва примери с използване на constexpr върху указател. Така че това, което опитвате, сега трябва да е възможно, въпреки че сега трябва да използвате ключовата дума constexpr вместо const от най-високо ниво.

Има и включена грешка в gcc, g++ отхвърля собствените примери на стандартната чернова за валидно constexpr използване.

person Ben Voigt    schedule 12.09.2011
comment
Всъщност опитах всичко това с constexpr, със същите резултати като в другите ми коментари... - person Kerrek SB; 12.09.2011
comment
1) &var не е интегрална, нали? 2) Искате да кажете, че не знам 3) Какво ще кажете за коментара на @Kerrek SB? - person Armen Tsirunyan; 12.09.2011
comment
@Kerrek: gcc не прилага напълно C++0x в 4.5.1. Имате ли по-нова версия, с която да тествате? - person Ben Voigt; 12.09.2011
comment
Тествам с 4.6.1. Трябва да попитаме някой, който има версията за моментна снимка, но съобщението за грешка изглежда доста конкретно и преднамерено, така че бих се изненадал, ако това просто е внедрено погрешно. - person Kerrek SB; 12.09.2011
comment
@Armen: Не мисля, че &var е постоянен израз в C++03. Действителните параметри на шаблона трябва да се поберат в една от няколко категории, константните изрази са едно, а указателите са друго. C++0x обединява тези концепции. - person Ben Voigt; 12.09.2011
comment
@Kerrek: Бихте ли пробвали кода на ideone.com/hSPrJ? Копиран е директно от n3290, долната част на страница 127. - person Ben Voigt; 12.09.2011
comment
@Ben: Test<xp> дава същата грешка за това, че xp е променлива. Test<&x> се проваля because ‘x’ does not have external linkage. Test<addr(x)> се проваля с could not convert template argument ‘addr((* & x))’ to ‘const int*’ (Самите три декларации са добре.) - person Kerrek SB; 12.09.2011
comment
@Kerrek: И какво, ако дадеш на x външна връзка? - person Ben Voigt; 12.09.2011
comment
@Ben: с extern const int x = 5;, Test<&x> работи. Но това е просто връщане към първия ви пример, предполагам. addr(x) все още не работи. - person Kerrek SB; 12.09.2011

Проблемът е, че вашата C++ програма може да бъде заредена във всяка точка на паметта и така адресът на глобален var може да е различен всеки път, когато стартирате програмата. Какво се случва, ако стартирате програмата си два пъти? Тогава var очевидно е на две различни места.

Дори по-лошо, във вашия пример вие вземате адреса на променлива в стека! виж това:

void myfunction( unsigned int depth) {
     const int myvar = depth;
     const int * const myptr = &myvar;
     if (depth)
         myfunction(depth-1);
}

Ако main извиква myfunction(3), тогава 3 myvars се създават на отделни места. Няма начин времето за компилиране дори да знае колко много myvars са създадени, още по-малко има точни местоположения.

И накрая: декларирането на променлива като const означава: "Обещавам" и не означава, че това е константа за време на компилиране. Вижте този пример:

int main(int argc, char** argv) {
    const int cargc = argc;
    char* myargs[cargc]; //the size is constant, but not a _compile time_ constant.
}
person Mooing Duck    schedule 12.09.2011
comment
Знам разликата между const и постоянен израз. Вашият отговор противоречи на добре оформената част от втория ми пример. И също така противоречи на първия добре оформен пример. - person Armen Tsirunyan; 12.09.2011
comment
Хммм... Не съм сигурен дали вярвам в това. В крайна сметка Test<&var> работи въпреки предполагаемото несигурно местоположение на var. - person Kerrek SB; 12.09.2011
comment
Указателят е постоянен и се инициализира по време на компилиране. Стойността, която виждате в указателя, не е абсолютна, а отместване от абсолютен адрес, който операционната система знае. Дори ако програмата се изпълнява два пъти по едно и също време, указателите могат да имат една и съща стойност, но в действителност да сочат към два различни адреса. - person wilhelmtell; 12.09.2011
comment
Стойностите в указателя са абсолютни виртуални адреси. Множество едновременни екземпляри на програмата са разрешени чрез магията на превода на виртуални › физически адреси; всеки процес има своя собствена таблица за превод. - person Ben Voigt; 12.09.2011
comment
А, добре, забравих за виртуалната памет. Е, има още един напълно грешен отговор за мен. @Kerrek SB: това е много солидно доказателство. - person Mooing Duck; 12.09.2011
comment
@Mooing: Въпреки че правите добра гледна точка относно последния фрагмент в примера -- вземането на адреса на нестатична локална променлива, дори в main, не може да доведе до постоянен израз. Мисля, че разширението g++, позволяващо VLA дори в режим C++, е в действие тук. - person Ben Voigt; 12.09.2011
comment
@Armen: Дори да се компилира, не трябва да се компилира. Много операционни системи ще променят мястото във виртуалната памет, където се зареждат програмите. Дори адресите на статичната памет ще се променят. Невъзможно е компилаторът да знае по време на компилиране какъв ще бъде виртуалният адрес на която и да е променлива. - person Nicol Bolas; 12.09.2011
comment
@Nicol Bolas: Нямам представа как се прилага, но съм почти сигурен, че моят първи пример е добре оформен. Не съм 100% сигурен за втория обаче - person Armen Tsirunyan; 12.09.2011
comment
@Nicol Bolas: Наистина, както подсказва отговорът на Йоханес, и двата ми добре оформени примера са законни в C++11. Вторият ми пример обаче е легален само в C++11 - person Armen Tsirunyan; 13.09.2011