Pull to refresh

Comments 95

1. С парой все просто

std::map<char, int> myMap;
myMap.insert(std::make_pair('a', 10));


2. Почему ты решил, что этот auto сможет правильно разобрать, что нужно?
class A
{
   A(int, int);
};

class B
{
   B(int, int);
};

class C
{
    C(std::pair<int, int>);
    C(A);
    C(B);
};

C obj = Auto(10, 10); // ???

А в чем разница с этим:
struct A {
A(signed char v): v(v){}
A(unsigned char v): v(v){}
int v;
};
A a(1); //???

если у вас несколько неявных кандидатов, то уж извините
Дык, дело то в том, что в таком случае я напишу

struct A {
   explicit A(signed char v): v(v){}
   explicit A(unsigned char v): v(v){}
   int v;
};

A a(1); // ошибка компиляции
A a('a'); // ошибка компиляции
A a((unsigned char)'a'); .// ok


и проблема решена.

А теперь посмотрим на случай с этим auto

struct A
{
   explicit A(int, int);
};

struct B
{
   explicit B(int, int);
};

struct C
{
    explicit C(const std::pair<int, int>&);
    explicit C(const A&);
    explicit C(const B&);
};

std::vector<C> vec;
vec.push_back(A(10, 10));
vec.push_back(B(10, 10));
vec.push_back(std::make_pair(10, 10));
vec.push_back(Auto(10, 10)); // ??? что здесь будет

Вы опередили меня, только написал комментарий, как заметил ваш)
Не сработает в С++11. Тип std::initializer_list может хранить только один тип данных.
auto t = {1.0, 2.0, 3.0};//t — std::initializer_list.

В стандарт хотят(или уже) включить автоматическое создание пары и кортежа
auto pair = {'a',9};//std::pair<char,in>
auto cort = {'a',9,10.0};//std::tupple<char,in,double>
но синтаксис через {} относится не только к спискам инициализации. В некоторых контекстах это как раз-таки создание объекта через конструктор с подходящей сигнатурой.

Все прекрасно сработает именно в С++11. Синтаксис {} в данном случае не имеет никакого отношения к std::initializer_list.

Сработает, потому что это не std::initializer_list.


В обычной инициализации структур используется тот же синтаксис и никаких проблем не возникает, хотя типы разные. (Это было еще в С.)


struct S {int a; const char *b;};
S s = {1, "2"};
можно использовать метод emplace, который сразу вызовет конструктор:
std::map<int, std::string> map;
map.emplace(1, "text1");

http://cpp.sh/6rkh
Метод insert шаблонного класса map ожидает четко указанный тип, но нам приходится писать «pair<char, int>» снова и снова при каждом вызове. Хорошо если наш тип простой, а если там шаблон на шаблоне и шаблоном погоняет?

Для таких случаев есть синтаксис универсальной инициализации.
Вместо вашего громоздкого
myMap.insert(auto('a', 10));
можно написать так
myMap.insert({'a', 10});

Еще немного, и C++ с PHP поменяются местами.
Мне кажется, что auto лишь усложнит процесс отладки, ведь так?
На самом деле нет.
Если интересно, почему лучше использовать auto, чем не использовать — советую ознакомиться с 5 главой книги «Effective Modern C++» Скотта Майерса — она целиком посвящена этому ключевому слову и примерам его использования.
На самом деле, процесс отладки auto только усложняет, по сути функция auto сводится к сокрытию ужасающего нагромождения шаблонных костылей. Если я использую какую-либо стороннюю библиотеку то хочу видеть что это за переменная, так я смогу понять какие операции с ней я смогу сделать. Нагромождение шаблонов и без того это скрывает, а auto вообще сводит на нет желание разбираться в чужом коде, и надо уповать только на хорошую документацию, что бывает далеко не всегда.
А просто не нужно использовать auto так, когда оно приводит к усложнению читаемости кода. Тип переменной должен быть очевиден из одной строчки.

К тому же, есть случаи, когда auto используется для неявного вывода типа, например:
auto z = x + y, где типы переменных x и y являются шаблонными.
К сожалению нужно-не нужно, это не более чем демагогия об идеальном программном коде. Сколько бы минусов не поставили мне и плюсов вам, код не станет лучше, а auto только будет сбивать с толку и усложнять код. Длинный и запутанный тип нельзя простым укорачиванием до слова auto упростить и сделать ясным и понятным.
Ну почему же? Упростить decltype(std::declval() + std::declval()) до auto вполне можно.
Или std::map<std::string, std::tuple<int, float>>::const_iterator до auto в цикле for.
А вот писать auto x = 1 нельзя — за такое надо бить по рукам.
На правах своего мнения скажу, что C++ он вообще такой, он именно и состоит из пересекающихся шаблонов, и если программист не умеет их читать какими бы большими они не были то ему надо научиться. Мне проще читать std::map<std::string, std::tuple<int, float>>::const_iterator, я понимаю что это, для чего это и что с этим я могу сделать, я сразу вижу свойства, а увидев auto мне надо рыть, что там вообще такое в принципе или анализировать нижеследующий код и пытаться по операциям с этой auto предсказать что это за тип. Auto может быть полезен для новичков, но они впоследствии столкнутся с этой же проблемой. В общем на мой взгляд auto не сочетается с природой C++. А так да, вроде бы удобно, но только на бумаге.
а вы увидев begin/end над контейнером не понимаете, что он возвращает итератор?
void f(std::map<std::string, std::tuple<int, float>>& map)
{
auto it = map.Begin();
std::map<std::string, std::tuple<int, float>>::const_iterator end = map.End();
...
}

По вашему второй вариант лучше? Повторять длинное описание типа каждый раз, когда потребуется объявить переменную производного типа?
Тем более, что тип переменной очевиден.
Я не противник auto, мне это ключевое слово нравится, но, справедливости ради, в данном случае я бы переписал код так:
using TextValueMap = std::map<std::string, std::tuple<int, float>>;
void f(TextValueMap& tvmap)
{
TextValueMap::iterator Uter = tvmap.begin();
/*...*/
}

Зачем? std::map::begin() всегда возвращает iterator или const_iterator.
Если через какое то время вы поменяете сигнатуру функции f на


void f(const TextValueMap& tvmap)

то вам придется изменять также строку


TextValueMap::iterator Uter = tvmap.begin();

на


TextValueMap::const_iterator Uter = tvmap.begin();

Компилятор конечно подскажет, но зачем отвлекаться?
Использование auto в данном случае не вносит никаких неоднозначностей, упрощает чтение кода и облегчает его поддержку. Так зачем усложнять?

Мой ответ относился не к использованию слова auto в коде, а к сокращению размеров записи с помощью выражений using, так как предыдущий комментатор высказался именно на тему объёмов писанины. Ключевое слово auto, всё-таки, создано не только и не столько для этого.

И оффтопом
В Qt5 проход по контейнерам с ключами в новом стиле for даст весьма забавный эффект. В следующем коде i будет типа short, а не QPair <int,short>, как было бы с std::map и std::pair<int,short>, соответственно.
	QMap<int,short> m; m[0]=1; m[1]=2;
	for (auto & i: m) {
		qDebug() << i; // 1 2
	}

Это достаточно логично с точки зрения поведения контейнера, но не самое ожидаемое поведение с точки зрения программиста, и может быть источником проблем, особенно, в шаблонах.
Мне, кстати, интересно, как такого эффекта удалось добиться.
И оффтопом

там итератор странно реализован: operator * вместо пары ключ/значение возвращает значение, но имеет методы key/value
В данном случае auto действительно удобно. iCpu, как я понял, имеет в виду, что есть достаточное количество ситуаций, в которых auto делает код менее очевидным. Например, если бы map в вашем примере был не аргументом функции прямо на строчку выше, а, например, полем класса, то было бы уже не так неудобно:

auto it = _map.begin();


Как тут тип понять какой тип у мапы? Что если _map — константная и const_iterator вернёт? И это очень простой пример.

Я стараюсь применять auto как можно реже и только там, где тип очевиден из контекста максимум несколькими строками выше. Например, вот в таких конструкциях:

auto thePointer = dynamic_cast<TYPE *>(theBasePointer);


Что касается циклов с итераторам — на мой взгляд, не так часто в прикладном коде возникает необходимость использования итераторов напрямую. Чаще range-based, где не нужно использовать итераторы. Логику, где нужно оперировать итераторами напрямую, можно спрятать в функции (и, как правило, это обосновано дизайном).
В данном случае auto действительно удобно. iCpu, как я понял, имеет в виду, что есть достаточное количество ситуаций, в которых auto делает код менее очевидным. Например, если бы map в вашем примере был не аргументом функции прямо на строчку выше, а, например, полем класса, то было бы уже не так неудобно:

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

Как тут тип понять какой тип у мапы? Что если _map — константная и const_iterator вернёт? И это очень простой пример.

А вы тогда сразу cbegin вызывайте — проблем не будет.
вы забываете про случаи, когда тип иррелевантен в контексте использования. Например:
auto itMax = std::max_element(begin(_records),end(_records));
А вот писать [...] нельзя — за такое надо бить по рукам.

Меня всегда интересовало — так где грань? Пока я для себя не нашел однозначного ответа.
надо лишь добавить, что всё-таки лучше воздержаться от некоторых советов Майерса:
auto highPriority = static_cast<bool>(features(w)[5]);
А здесь разве не всегда bool тип будет? Вроде тип описан справа, поэтому слева ставим auto.
там в примере features типа std::vector, operator[] которого возвращает не bool, а прокси класс, эмулирующий ссылку на бит.
Не особо, а, например, в случае for (auto item: someContainer) — auto вообще в самый раз — какого типа someContainer — неважно, более того его можно спокойно менять vector/list/set… не переписывая при этом весь код, где по нему итерируются.

Единственное, на что следует обращать внимание — чтобы область видимости «auto» не выходила за пределы функции или блока. Это правило очень хорошо ограничивают ООП и обязательное разделение класса на .h и .cpp файлы — нельзя объявить метод класса, возвращающий auto, а реализацию написать в .cpp
сори, тип контейнера ни при чем, но тип элемента контейнера тоже менять можно, например всякие int, int8_t qint8 и их 16, 32, 64 сородичи и т. д.
А так не лучше будет?
for (const auto& item: someContainer)
это уже зависит от ситуации

На самом деле, почти всегда лучше писать


for (auto&& item: someContainer)

Если someContainer::iterator::operator*() возвращает ссылку или константную ссылку, то item таковым и будет, если rlalue ссылку то возвращаемое значение будет перемещено в item без создания временного объекта.

в некоторых случаях может резолвнуться в неконстантную ссылку, а мутабельный доступ к контейнеру с implicit shared memory (например, как в контейнерах Qt) приведет к лишнему копированию данных
возвращаемое значение будет перемещено в item без создания временного объекта

Чего??? item в данном случае является ссылкой (вы пытастесь воспользоваться мейерсовской концепцией "универсальной сслыки"). Ничего в item перемещаться, разумеется, не будет.


Никаких временных объектов не создается и в огигинальном варианте с lvalue-ссылкой.

Хм. Если имеется в виду range-based цикл, я совсем не понимаю почему тут-то нельзя указать тип содержимого контейнера. Места займёт не так много — не нужно городить шаблонные конструкции с итераторами. А код понятнее станет. Сразу ясно, что смысл: мы перебираем элементы некой коллекции. Не важно какой, важно элементы какого типа в ней лежат (и это действительно важно — ведь мы в теле цикла будем оперировать объектами этого типа).
Если не секрет — объясните, что я не так написал? Я серьёзно. Не хотел никого троллить или флудить. Действительно хочу понять — какой смысл прятать тип элементов коллекции в range-based циклах (если речь действительно о них шла)?
UFO just landed and posted this here
Чтобы, если потом окажется, что в целях оптимизации надо заменить std::list на std::vector, достаточно было поменять тип в одном месте. Меня самого совсем недавно auto спас от сотен строк кода на шаблонах или макросах (нужна была таблица, корректно отображающая одномерные/двумерные вектора любых числовых типов)
auto f = [](){}; //указатель на функцию

#include <type_traits>

int main() {
    auto f1 = [](){};
    static_assert(std::is_pointer<decltype(f1)>::value, "f1 is not pointer");
}

fp.cpp:5:5: error: static_assert failed "f1 is not pointer"
    static_assert(std::is_pointer<decltype(f1)>::value, "f1 is not pointer");
    ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.


Это всё-таки лямбда, а не указатель (в частности, штука с потенциально дорогим копированием).
Вам дорого копировать 1 байт?
Где вы тут 1 байт увидели?
А вот тут указатель на функцию:
auto f1 = +[](){};
UFO just landed and posted this here
В данном случае — да. Поэтому написано «потенциально» (на практике — в случаях, когда тяжелые объекты захватываются по значению).
>Но к сожалению в языке C++ пока нет такой методики создания объектов
И я очень надеюсь что она и не появится. Ибо чревато всякими неожиданными эффектами
К счастью
в языке C++ пока нет такой методики создания объектов
Так как она никак не сочетается с шаблонами и перегрузками функций.
template<int length, typename Type>
class SomeClass {
public:
SomeClass(Complex c);
private:
...
}
template<int length, typename Type>
void f(SomeClass<length, Type> some);

f(SomeClass<10, int>(1.0));
f(Auto(1.0));//???

В первом варианте компилятор найдет неявное преобразование из double в Complex.
Во втором придется указывать шаблонные параметры
Дико бесит когда встречаю в чужих c# исходниках var. Приходится переключаться с понимания задачи на поиск того, что в этом var хранят сейчас и auto такой же трэш.
Зато с auto итерирование по коллекциям перестало быть болью
Кажется что да, наверно правильно, а потом понимаешь, что чтобы узнать про тип объектов в коллекции придётся IDE напрягать.
Обычно, где-то неподалеку есть объявление самой коллекции, а так да. Но это цена которую лично я готов заплатить за удобный for

Еще со времен С твердили: учитесь и писать и читать type-agnostic код. В не-декларативных statements языков С и С++ не должно быть никаких упоминаний конкретных типов. Именам типов место в декларациях и только в декларациях. Type-agnostic код прекрасно читаем, надо только набраться смелости и отбросить в сторону костыли, которыми являлись постоянные упоминания имен типов.


Но дурные привычки-костыли продолжают жить: молодежь тупо настаивает на явном приведении типа результата malloc в С или использовании имен типов в sizeof, ибо так якобы "надо, чтобы знать с каким типом мы работаем". И несмотря на все усилия более продвинутой мировой C/C++ community, манера тащить за собой имена типов куда надо и куда не надо умирать никак не хочет, ни в С, ни в С++.


Надеюсь, что привлекательность новых возможностей современного С++ победит дурные привычки в С++. А вот как навести порядок в рядах С-шников — не ясно.

Еще со времен С твердили: учитесь и писать и читать type-agnostic код


Кто твердил и чем именно это обосновывалось?
UFO just landed and posted this here
Вообще, я вот подумал — у меня пока не было опыта работы с крупным С++ проектом, в котором активно использовалось бы auto. Негативно отношусь к нему в большой степени из-за воспоминаний, оставленных нестрого типизированными скриптовыми языками (python и action script), большой код в которых часто воспринимался очень трудно.

Может, вы знаете какой-нибудь open source проект на С++, который активно использовал бы на type-agnostic код? Я бы глянул. На примере анализа кода подобного проекта можно было бы понять насколько хорош (ну, или плох) auto.
UFO just landed and posted this here
и то скорее в качестве документации


На мой взгляд, одной из главных задач языков высокого уровня является как раз-таки умение быть документацией. И чем более подробной — тем лучше. Я не говорю, что код при этом не должен быть лаконичным — слишком много текста делается код менее читабельным. Однако, как по мне, краткость не должна быть самоцелью.

На мой взгляд, несколько секунд, сэкономленные на указание полного типа, могут обернуться минутами затрат на ознакомление с кодом нового человека… Увы, как я уже писал выше, более или менее объективно рассудить нас сможет только проект, активно использующий auto. Если как-нибудь столкнусь с таким и не забуду о нашем разговоре — можно будет рассмотреть его в качестве примере.

P.S.: Кстати, с учётом частых дискуссий по теме auto (не только на хабре — среди моих знакомых программистов эта фича С++11 была воспринята наиболее противоречиво и часто вызывает жаркие споры), на мой взгляд, была бы полезной целая статья с разбором плюсов и минусов auto на примере анализа кода какого-нибудь реального open-source проекта. Возможно, подобная статья вывела бы тему использования auto из категории «религиозных» тем. Если когда-нибудь столкнусь с подобным проектом и на тот момент не будет статьи — могу написать. Как думаете, имеете смысл? Или, возможно, вы знаете какую-нибудь существующую статью аналогичного содержания?
UFO just landed and posted this here
несколько секунд, сэкономленные на указание полного типа, могут обернуться минутами затрат на ознакомление с кодом нового человека…

Не знаю у кого как, но у меня ознакомление с новой кодовой базой всегда происходит непросто. Вот только (опять же, лично мне) при этом всё равно важнее логика происходящего, а не типы. Ну серьёзно, чем поможет какой-нибудь Entity<Run<Some>>? Да, можно скрупулёзно изучить все составляющие части, а потом ещё иерархии классов, специализации шаблонов и т.д. Но появляется риск забыть, что собственно искали. (:


Опять же, в моём опыте даже в весьма качественных проектах бывали "странные" именования у типов (и модулей). То есть, всё равно надо вникать в предметную область и код. Снова сошлюсь на личный опыт — мне проще постепенно расширять область понимания. Если закапываться в типы и иерархии, то это больше сбивает.


Справедливости ради, скажу, что IDE (в моём случае, QtCreator) не всегда может показать тип, если там "несколько уровней" auto и это действительно бывает неудобно.


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

Ох как вы будете тогда злиться от новых возможностей C# 7 (анонимные классы).
А ещё существуют языки с динамической типизацией типа Python: там каждая переменная — это var.
Когда пишу на языках с var'ами — я к этому готов, они изначально таковы, а в с++ мне это кажется лишним. Если это используют там, где предполагается — то ничего особо страшного(да и не особо нужно-то). Но ведь будут куда ни попадя auto писать.
UFO just landed and posted this here
UFO just landed and posted this here

У вас по всему тексту встречаются такие вот конструкции:


template<typename... Types>
constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>((Types&&)args...);}

Что вы хотели сказать вот этим: ((Types&&)args...)?
Мне кажется, в данном случае будет уместнее использовать std::forward:


template<typename... Types>
constexpr Container<Types...> Auto(Types&&... args) {return Container<Types...>(std::forward(args)...);}

Выглядит так, как будто вы замышляли идеальную передачу, а выполняете странное приведение типов в стиле C.
кроме того, вот эта фраза


Все аргументы передаются в качестве rvalue аргументов до самого конечного получателя Value, чтобы не потерять ссылки.

Явно свидетельствует, что вы путаете rvalue и универсальные ссылки.

Все-таки ((Types&&)args...) — это тоже идеальная передача, и работает это так же, как std::forward().

Совсем нет. Сравните: С-style-cast и std:forward


1) When the C-style cast expression is encountered, the compiler attempts to interpret it as the following cast expressions, in this order:
a) const_cast<new_type>(expression);
b) static_cast<new_type>(expression), with extensions: pointer or reference to a derived class is additionally allowed to be cast to pointer or reference to unambiguous base class (and vice versa) even if the base class is inaccessible (that is, this cast ignores the private inheritance specifier). Same applies to casting pointer to member to pointer to member of unambigous non-virtual base;
c) static_cast (with extensions) followed by const_cast;
d) reinterpret_cast<new_type>(expression);
e) reinterpret_cast followed by const_cast.

Возможная реализация для std:forward (g++ bits/move.h):


template<typename T>
constexpr T&& forward(typename std::remove_reference<T>::type& t) noexcept
{
    return static_cast<T&&>(t);
}
template<typename T>
constexpr T&& forward(typename std::remove_reference<T>::type&& t) noexcept
{
    static_assert(!std::is_lvalue_reference<T>::value, "template argument substituting T is an lvalue reference type");
    return static_cast<T&&>(t);
}

Видите разницу?


Дополнително: 1 2 3 4

Мне всегда было интересно, в каких случаях (реальных) возможно срабатывание assert во втором forward и почему нельзя просто писать static_cast<T&&> вместо std::forward?

Это такая защита от не правильного использования. std::forward предполагает использование именно с шаблонным параметром, но если кто-то вдруг захочет странного и напишет что то вроде этого:


std::forward<int&>(5)

то сработает защита. В этом случае аргумент является rvalue, поэтому компилятор выбирает вторую перегрузку, но тип ссылка на lvalue. пример

Функциональной разницы нет.


Вторая версия функции предназначена только для того, чтобы предотвратить ошибки/хаки типа std::forward<int &>(42), т.е. ситуации, когда пользователь случайно или намеренно указывает "нелегальную" комбинацию типа и значения. То есть вторая версия существует только ради этого static_assert внутри. Если бы мы не задавались целью следить за корректностью использования std::forward, то достатчоно было бы первой перегрузки.

Я вижу только дополнительный static_assert() и защиту от неправильного использования.


Но я не могу придумать пример, когда (T &&)value не сработало бы как идеальная передача, если T — тип универсальной ссылки, выбранный компилятором автоматически. Можете привести такой пример?

Но, самое главное, совершенно не ясно, почему вы проигнорировали вариант
myMap.insert({ 'a', 10 });
К сожалению, как я уже сказал, я не знал про списки инициализации. я думал, что это применимо только к инициализации переменных
MyObj obj = {1, 2, 3};
и по этому мне пришлось изобретать велосипед.
У типа int нет и не может быть конструктора
понятно, что у int нет конструктора, я просто привел простой пример.
Что вы хотели сказать вот этим: ((Types&&)args...)?
если конечный конструктор принимает ссылку на объект, то без этого преобразования конструктор будет пытаться получить ссылку на промежуточную переменную, а не на первоначальный объект.

Спасибо всем за обсуждение, вы повысили мои познания языка. Где как не в споре рождается истина.
когда можно было просто присвоить к переменной a значение 10

int a = 10 — это не присваивание, а инициализауция.


Или в крайнем случае вызвать его конструктор:

У типа int нет и не может быть конструктора. Конструкторы в языке С++ бывают только у класс-типов. int — это не класс-тип.


Более того, статься совершенно не объясняет, зачем все это нужно. Пример с map::insert — мимо кассы, обо эта задача в С++ давно решается униформной инициализацией


myMap.insert({ 'a', 10 });

Причем этот вариант превосходит все предложенные варианты тем, что сразу создает пару правильного типа — std::pair<const char, int> — обратите внимание на наличие const перед char. Забывать указывать этот const — популярный огрех среди начинающих программистов. Это приводит к лишней промежуточной конверсии. К сожалению, от аналогичной проблемы же страдает и популярный вариант с make_pair. И от такой же проблемы страдает и ваш вариант.


Но, самое главное, совершенно не ясно, почему вы проигнорировали вариант


myMap.insert({ 'a', 10 });

который в данном как раз и будет выбирать способ инициализации автоматически именно по типу параметра.


Также вызывает недоумение ваше


auto f = [](){}; //указатель на функцию

Хоть "captureless" лямбды и приводимы к указателю на функцию, в данном случае f — отнюдь не указатель на функцию, а все таки специальный функциональный объект (closure object).

В С++ есть масса замечательных вещей, которые умиляют. На мой взгляд, первым идет оператор «сиськи» operator()(), а вторым — ваши три пары разных скобок подряд [](){}.
UFO just landed and posted this here
UFO just landed and posted this here
Что за бред я только что прочитал? Вы предлагаете функциональность, которая позволит сломать какой-либо код написав что-то в другом месте. Вы случайно не любитель садо-мазо?
Только что увидел такой код:
auto main() -> int
{
   ...
}
Вопрос: зачем?
Скорее всего для того, чтобы выпендриться.
а вот пример, где описание типа в конце полезно:
static auto selfTypePtr() ->decltype(this);

Кстати интересный пример — статический метод использующий this

Очередная GCC-шная самодеятельность (либо просто дыра). Использование this в объявлениях статических функций-членов запрещено языком. Clang корректно отлавливает эту ошибку. GCC пропускает, даже в режиме -pedantic-errors.

А мне не нужен this, мне нужна только декларация. Функция никогда не будет вызываться, у нее даже тела нет. С помощью ее я могу узнать собственный тип там где обычно это невозможно.

Я понимаю. Однако спецификация языка однозначно утверждает, что this "shall not appear within the declaration of a static member function", даже несмотря на то, что "its type and value category is defined within a static member function as it is within a non-static member function".

Вот это явная дыра:
template<int value>
struct B {
	enum {VALUE = value};
};
struct A {
	int v1;
	int v2;
	static auto offset() -> B<(int)&this->v2 - (int)&this->v1>;
};
...
decltype(A::offset())::VALUE; //Что вернет такое выражение?
ошибку компиляции вестимо
В том то и дело, что нет, но результат всегда будет 0.

Это в каком это компиляторе, интересно?


GCC:
error: 'this' is not a constant expression


Clang:
error: non-type template argument is not a constant expression
note: use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function

ну, в частности у меня gcc version 4.9.2 (AVR_8_bit_GNU_Toolchain_3.5.3_1700) на других архитектурах и версиях не проверял
во многих других языках тип возвращаемого значения в функции указывается в конце объявления.
Sign up to leave a comment.

Articles