gammaker
> Нужна операция проверки существования типа, обратная enable if. То есть такое:
Написать свой шаблон?
http://en.cppreference.com/w/cpp/types/enable_if.
gammaker
> Не знаю, к чему ты привёл эту неаналогичную аналогию. Эти операции не обратны
> друг другу в отличие от тех, про которые я говорил.
Просто у меня операция обратная условному удалению, называеться условным добавление, но я всё понял. Можешь не продолжать.
Надеюсь никто не против если я тут для пары комментариев сделаю операцию обратную условной проверке сущестования?
ZeroCool++
> http://en.cppreference.com/w/cpp/types/enable_if.
Чего ты мне enable_if пихаешь? Он уже есть в стандартной либе и вообще очевидно реализуется. Дай мне лучше шаблон exists из #0.
Adler
> Просто у меня операция обратная условному удалению, называеться условным добавление
Ты не то обращаешь. Условное удаление - это следствие того, что тип enable_if<false> не существует. А обращать надо причину - операцию превращения false в несуществующий тип, а true - в существующий. То есть нужен шаблон, который даёт true, если ему подсунули существующий тип, и false, если ему подсунули несуществующий тип.
Всё, я уже не знаю, как ещё объяснить. Вы все меня троллите что ли?
gammaker, вот ты правильно написал:
>Так программу строят все, кто использует шаблоны на уровне чуть выше начинающего. Особенно те, кто пишет библиотеки общего назначения типа >STL\Boost
На придумывают сначала якобы универсальных библиотек. А потом начинается гадание, как их припилить к своему проекту, с типами которого эти унивесальные библиотеки ничего сделать не могут.
>Ну не понимаешь ты SFINAE
Я его не то чтобы не понимаю - не приемлю. У меня другая идеология. Я не могу врубиться как можно например создать тип
struct my_coord {float x; float y; float z};
и подствалять в унивесальный шаблон cos();
template< typename T > float cos(T grad );
И вызывать
my_coord pos_1; float result=cos(pos_1);
и затем разруливать эти косяки универсализма ... а перед вызовом этих косяков выяснять вообще создались они или были отвергнуты на этапе компиляции не вызвав ошибок компилятора. И это считается более просто и !универсально, нежели чем так вообще не делать ...
KKH
> Я не могу врубиться как можно например создать тип
У тебя пример какой-то неподходящий вообще. Допустим, все контейнеры реализуют метод push_back, который добавляет в конец контейнера новый элемент, а некоторые контейнеры имеют также метод reserve, который подготавливает контейнер, к тому, сколько в него элементов собираются запихать. У std::vector такой метод есть, а у std::list - нет.
Пусть нужно написать функцию, который добавляет в любой контейнер 1000000 случайных чисел:
template<typename C> void add_mrands(C& container) { for( int i=0; i<1000000; i++) container.push_back( rand( )); }
Если мы сюда передадим std::list<int> или std::vector<int>, то всё отработает. Но в случае с вектором ведь можно же было вызвать reserve, значительно ускорив работу этой функции! Но если мы добавим reserve, тогда сюда уже нельзя будет передать список. Компилятор выдаст ошибку, что список не содержит reserve. И вот чтобы разрешить эту ситуацию либо убирают шаблоны и делают всевозможные перегрузки под всевозможные контейнеры, либо делают две шаблонные перегрузки: отдельно для типов, имеющих reserve и для не имеющих.
Если сделать так:
template<typename C> void add_mrands(C& container) { for( int i=0; i<1000000; i++) container.push_back( rand( )); } template<typename C> void add_mrands( C& container) { container.reserve( 1000000); for( int i=0; i<1000000; i++) container.push_back( rand( )); }
То компилятор не поймёт, какую из этих двух функций вызывать и выдаст ошибку. Поэтому нужно сделать так, чтобы для каждого типа существовала только одна подходящая перегрузка:
template<typename C> std::enable_if_t<!has_reserve<C>::value> add_mrands(C& container) { for( int i=0; i<1000000; i++) container.push_back( rand( )); } template<typename C> std::enable_if_t<has_reserve<C>::value> add_mrands( C& container) { container.reserve( 1000000); for( int i=0; i<1000000; i++) container.push_back( rand( )); }
Тогда в зависимости от того, какой контейнер передашь в add_mrands, компилятором будет выбрана самая эффективная реализация. Ну как, убедил?
Опять сраные контрапционы, игру уже начинай делать.
Hardcode
> Опять сраные контрапционы, игру уже начинай делать.
Не, игра по планам лет через 5, не раньше.
gammaker
ты прав
/me засрал всю тему :(
>То есть нужен шаблон, который даёт true, если ему подсунули существующий тип, и false, если ему подсунули несуществующий тип.
В С++11 невозможно сделать шаблон чтобы потом его можно было использовать для проверки сущестования типа TYPE вот таким выражением:
if_exists<TYPE>::value
надо объяснять почему?
Зато можно использовать __if_exists от MS, или если зашить TYPE внутрь шаблона.
Или если TYPE - это выражение типа "FOO::BAR", где FOO - 100% существует, а BAR - это то существование чего нужно проверить, то запросто можно сделать шаблон для выражения "HasBAR<FOO>::value" используя SFINAE.
gammaker
Угадай что будет от такой оптимизации если на вход подать container размером на 1 элемент больше чем ты собераешся его сделать.
> Но в случае с вектором ведь можно же было вызвать reserve, значительно ускорив
> работу этой функции!
Adler
> Угадай что будет от такой оптимизации если на вход подать container размером на
> 1 элемент больше чем ты собераешся его сделать.
Ну вообще надо было сделать
container.reserve(container.size()+1000000);
Но в принципе ничего плохого не случится, потому что reserve не будет уменьшать контейнер. Просто он не сработает, если там уже 1000000 элементов.
В чем смысл этого бреда в компилируемом языке?
gammaker
> а некоторые контейнеры имеют также метод reserve, который подготавливает
> контейнер, к тому, сколько в него элементов собираются запихать. У std::vector
> такой метод есть, а у std::list - нет
просто интересно, как по твоему, почему нет?
gammaker
> Возможно ли сделать это в C++?
gammaker
> exists<std::enable_if_t<false>>::value //== false
В данном случае ты хочешь что б value существовало. Для этого шаблон exists должен быть построен. Что бы сгенерить шаблон с данным шаблонным аргументом, нужно что бы шаблонный аргумент существовал. Но так как он не существует, соответственно и шаблон не создастся.
Надо копать в другую сторону.
Есть несколько статей Филиппа Росина, возможно читал. http://b.atch.se/
Перевод первой статьи https://habrahabr.ru/post/268141/
Так вот, думаю можно сделать так:
void exists_set<my_type_tag, std::enable_if_t<false>>(){...};
// Внутри этой функции по аналогии с идеей Филиппа мы увеличивает счетчик для типа my_type_tag. Соответственно если std::enable_if_t<false> не существует, счетчик не увеличится.
А затем с помощью
exists_chek<my_type_tag>::value
проверяем значение счетчика. Если 1 или больше - тип существует. Если 0 - не существует.
struct t1{}; struct t2{}; exists_set<t1, std::enable_if_t<true>>(); exists_chek<t1>::value; // == 1 exists_set<t2, std::enable_if_t<false>>( ); exists_chek<t2>::value; // == 0
vater
> просто интересно, как по твоему, почему нет?
Потому что ему нечего резервировать, он всё равно элементы по одному выделяет.
denesik
> Есть несколько статей Филиппа Росина, возможно читал. http://b.atch.se/
> Перевод первой статьи https://habrahabr.ru/post/268141/
Он не советует использовать это в реальности. А у меня на этом будет построено всё с самых основ. Поэтому я лучше обойдусь тем способом попроще, который у меня уже есть. Он в принципе позволяет сделать всё, но не такой удобный, как был бы, если бы у меня был шаблон exists.
У меня сейчас такая штука:
template<typename... Expressions> using void_t = void; #if !defined(_MSC_VER) || defined( __clang__) #define DEFINE_EXPRESSION_CHECKER2( checker_name, expr, default1, default2) \ template<typename T1 default1, typename T2 default2, typename dummy=void> struct checker_name: std::false_type {};\ template<typename T1, typename T2> struct checker_name<T1, T2, void_t<decltype( expr)>>: std::true_type {}; #else #define DEFINE_EXPRESSION_CHECKER2( checker_name, expr, default1, default2)\ template<typename T1 default1, typename T2 default2> struct checker_name\ {__if_exists( void_t<decltype( expr)>) {enum: bool {value=true};}\ __if_not_exists( void_t<decltype( expr)>) {enum: bool {value=false};}}; #endif
Первая реализация неправильно работает на студии <=2013, поэтому для неё специально написана другая реализация, использующая расширение __if_exists компилятора студии.
Эта штука почти идеальна как в простоте реализации, так и по всеядности. Может проверить компилируемость любого выражения, а значит и существование любого типа, метода, функции и чего угодно ещё. Но проверяльщик каждого конкретного выражения надо объявлять заранее, то есть нельзя сделать такую штуку:
template<typename T> std::enable_if_t<COMPILES(T( ).empty( )) && std::is_class<T>::value> func( ) {...}
Если бы можно было проверить существование типа на ходу, я бы смог сделать такой макрос COMPILES. Чтобы исправить этот недостаток, я и создал эту тему, но наверное лучше не стоит с этим заморачиваться.
gammaker
>У тебя пример какой-то неподходящий вообще
Нормальный у меня пример.
По идеологии SFINAE компилятор должен подыскать преобразование struct my_coord в x,y,z в какой-то угол автоматически. И если такое преобразование есть - например угол между гипотетическим направлением из точки 0,0,0 в 1,0,0 и точки 0,0,0 и хранимыми x,y,z - то создать таки функцию COS по шаблону, которая будет употреблять struct my_coord как исходный аргумент. Если не найдёт - не создавать...
>Ну как, убедил?
Я понимаю что так можно. Возможно у меня действительно предвзятое мнение. Я считаю что так или иначе всё равно приходится разруливать спорные моменты. Только это простой пример когда это более менее понятно. Но в реальных проектах с программистами разных взглядов можно нагородить такими не простыми конструкциями - сложно предугадываемый код.
У меня был только негативный опыт работы в команде таким образом. Когда пишешь сам - как-то проще, т.к. знаешь что и как делаешь. На работе - банальный опыт - человек не мог написать конвертор из elf файла в бинарник используя vector. И он не новичок какой-то, а практически ведущий программист. Просто он не мог найти у себя в коде ошибку. Я на обычных контейнерах написал этот парсер за пару недель. Он поглядев мой код быстро понял в чём у него проблема, переписал и его вариант тоже стал рабочим. И его код был и понятен и элегантнее. Но вот затык свой он не мог преодолеть где-то пол года. Его любимый девиз: "а ларчик-то просто открывался ...".
И вот постоянно... как только в программе применяют vector, эти навороты с STL - программы пару лет крашаться, пока практическим опытом не выуживаются ошибки. Чтобы поправить какую-нибудь хрень - нужно недельку посидеть по изучать как оно вообще устроено ... А потом, а ... "а ларчик-то просто открывался ...".
KKH
> По идеологии SFINAE компилятор должен подыскать преобразование struct my_coord
> в x,y,z в какой-то угол автоматически.
А вот и нет. Тут нет вообще никакого SFINAE. Компилятор не в курсе вообще, что нужен какой-то угол. Он просто подставит вместо T структуру my_coord, увидит, что из неё нельзя посчитать косинус и ругнётся на внутренности этой функции, выдав какое-то невразумительное сообщение об ошибке во внутренностях.
SFINAE работает только если ошибка произошла в прототипе функции, но в твоём примере никакой ошибки не будет, потому что все типы, использующиеся в прототипе, существуют при любом T. А вот если бы ты добавил std::enable_if_t<is_angle<T>::value, float>, то компилятор просто бы сказал, что нет подходящей функции для вызова с my_coord, и не стал бы лезть внутрь.
KKH
> Я на обычных контейнерах написал этот парсер за пару недель.
И что это за обычные контейнеры такие, которые даже обычнее, чем vector?