LINUX.ORG.RU

ANSI C++: вычисление дробной степени (SFINAE)

 , , , ,


0

2

Всем привет!

Подскажите пожалуйста, как в классическом C++ можно с помощью шаблонов, принципа SFINAE посчитать результат возведения положительного дробного числа в положительную дробную степень во время компиляции.

Общая идея:

a^x = exp(x * ln(a))

В свою очередь ln(a) можно разложить в ряд Тейлора:

ln(a + 1) = [ a / (1!) ] - [ a^2 / (2!) ] + [ a^3 / (3!) ] - ...

Экспонента тоже раскладывается в ряд Тейлора:

exp(p) = 1 + [ p / (1!) ] + [ p^2 / (2!) ] + [ p^3 / (3!) ] + ...

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

Собственно вопрос: подскажите как можно обойти это ограничение и таки посчитать a^x в compiletime.

★★★

Последнее исправление: zx_gamer (всего исправлений: 1)

Ответ на: комментарий от zx_gamer

Что мешает описать find в IContainer и от него наследовать все остальные контейнеры?

Сама необходимость наследования.

Хотелось бы почитать причину отклонения вашего предложения по улучшению stl комитетом. Есть ссылка?

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

А что плохого в наследовании? Когда все в хедерах (а шаблоны все равно все в хедерах) компилятор сделает наследование бесплатным. Даже виртуальные функции он может развернуть.

Можно будет писать функции которые получают любой контейнер и что-то с ним делают (получают const IContainer&).

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

А что плохого в наследовании?

Накладные расходы.

Вы предлагаете для решения выдуманной проблемы, пусть и со «странным» синтаксисом, нагородить костылей и подпорок.

andreyu ★★★★★
()
Ответ на: комментарий от zx_gamer

Вообще-то это общепринятая практика. Так делают в Qt, в Java и т.п.

Так себе аргумент. Они зажаты в рамках своей парадигмы и вынуждены все делать через нее, даже если это идет в ущерб производительности и ясности кода. В этом их сила, в этом их слабость. С++ это другой язык.

soomrack ★★★★
()
Ответ на: комментарий от andreyu

В первом случае компилятор сгенерит два ПОЛНЫХ комплекта всех методов класса для каждого типа вектора.

В машинный код попадет только то, что будет использоваться.

В первом случае find нужно писать для каждого контейнера. Во втором случае find написан один раз для произвольного контейнера.

Не нужно. Реализация прописывается 1 раз для всех классов, реализующих интерфейс итераторов с линейным доступом.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от andreyu

Да в компайл тайме все задачи решать! И со SFINAE! А которые нельзя решить в компайл тайме - тем хуже для них, отменить такие дурацкие задачи.

seiken ★★★★★
()
Ответ на: комментарий от zx_gamer

Нет, просто STL это плохо спроектированная библиотека.

По сегодняшним меркам – да, об этом прямо говорят топовые программисты С++. Но не стоит забывать, что она проектировалась в начале 90х годов и тогда она была очень современной, позволила решить кучу проблем и способствовала развитию программирования, в т.ч. и тому, что теперь она кажется плохо спроектированной. За 30 лет столько всего изменилось в понимании того как надо проектировать код, как его писать, как тестировать…

soomrack ★★★★
()
Ответ на: комментарий от soomrack

Да не, Qt тоже был в 90-е написан. И он выглядит адекватно. А STL (по крайней мере в РФ) стало видно только году в 2000. И к тому моменту очень много программистов C++ очень сильно ругались по этому поводу.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

Да не,

Не да.

Qt тоже был в 90-е написан.

Qt был написан уже после того, как Степанов и Ли сделали первый вариант STL и этот вариант начали затаскивать в стандарт C++ (которого тогда еще не было). ЕМНИП, Qt – 1994-й, STL (который еще не standard template library) – 1992-й.

При этом разработчики Qt не стали связываться с шаблонами C++, которые тогда еще не везде были доступны. Поэтому у них выбора не оставалось кроме как пердолиться с контейнерами в стиле ООП.

А STL (по крайней мере в РФ) стало видно только году в 2000.

Ну надо же, РБ года так на 4-е обогнал РФ ;)))

ЕМНИП, даже Visual Studio 6.0 уже шел с куцей версией STL (что понятно, т.к. VS6.0 появился в один год со стандартом C++98).


Вот скажите честно, задумывались ли вы вот о чем: допустим, есть некий базовый ICollection<T> и некий базовый Iterator<T>. Тогда:

  • как вы себе видите возможность сравнения итераторов от двух разных контейнеров? Например, можно ли сравнить на равенство итератор от Vector с итератором от Set?

  • как вы себе видите эффективную поддержку сдвига итератора на N позиций влево/вправо? Ведь у вас же некий базовый Interator<T>, глядя на который мы не знаем, получен ли он для Vector, OrderedSet, UnorderedSet, DoubleLinkedList, ForwardList и т.д.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

как вы себе видите возможность сравнения итераторов от двух разных контейнеров? Например, можно ли сравнить на равенство итератор от Vector с итератором от Set?

Не совсем понял вопрос. Вы хотите проверить, равны ли итераторы, которые указывают на объект в разных контейнерах?

как вы себе видите эффективную поддержку сдвига итератора на N позиций влево/вправо? Ведь у вас же некий базовый Interator<T>, глядя на который мы не знаем, получен ли он для Vector, OrderedSet, UnorderedSet, DoubleLinkedList, ForwardList и т.д.

Есть общий интерфейс итераторов, который может только на 1 двигаться, есть более детальные (например для random access iterator), которые добавляют к общему новые возможности. Например ForwardList наслудется только от общего IIterator, а Vector, Array и т.п. от IRandomAccessIterator.

zx_gamer ★★★
() автор топика
Последнее исправление: zx_gamer (всего исправлений: 1)
Ответ на: комментарий от zx_gamer

Не совсем понял вопрос.

Я не удивлен исходя из ваших оценок качества STL.

Вы хотите проверить, равны ли итераторы, которые указывают на объект в разных контейнерах?

У меня есть два Iterator<T> хз как и откуда полученных. Могу ли я сравнить их на равенство или неравенство?

Есть общий интерфейс итераторов, который может только на 1 двигаться, есть более детальные (например для random access iterator), которые добавляют к общему новые возможности. Например ForwardList наслудется только от общего IIterator, а Vector, Array и т.п. от IRandomAccessIterator.

Т.е. вы хотите, чтобы в C++ было что-то такое:

template<typename T> class Iterator { ... };
template<typename T> class RandomAccessIterator : public Iterator { ... };

template<typename T> class ICollection {
public:
  virtual Iterator<T> begin() = 0;
  ...
};

template<typename T> class Vector : public ICollection {
public:
  RandomAccessIterator<T> begin() override { ... }
  ...
};

Я правильно вас понимаю?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Я правильно вас понимаю?

Ну грубо говоря да.

У меня есть два Iterator<T> хз как и откуда полученных. Могу ли я сравнить их на равенство или неравенство?

Вы хотите получить ответ на вопрос «на одинаковый ли объект они указывают?» Если да, то достаточно сравнить адреса.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

Вообще-то это общепринятая практика. Так делают в Qt, в Java и т.п.

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

Плюсовая std - прекрасна. Одно только не нравится - километровые портянки в консоль при ошибках, но это не совсем проблема std, можно дополнительно парсить выхлоп и резать шаблонные параметры сущностей, если копать глубоко не нужно

kvpfs_2
()
Ответ на: комментарий от zx_gamer

Ну грубо говоря да.

Тогда как вы в C++ собираетесь поддерживать подобную ковариантность возвращаемых по значению итераторов?

Вы хотите получить ответ на вопрос «на одинаковый ли объект они указывают?»

Поскольку мы говорим про итераторы, то меня интересует узнать равны ли итераторы друг другу.

Посему в очередной раз повторяю вопрос: есть два экземпляра Interator<T> про которые я не знаю откуда и как они получены. Могу ли я их сравнить на равенство или неравенство.

Да или нет?

eao197 ★★★★★
()
Ответ на: комментарий от kvpfs_2

Плюсовая std - прекрасна.

Офигительна просто.

if ((t = std::find(my_vector.begin(), my_vector.end(), value) != my_vector.end())

А аллокаторы на шаблонах как прелесть знаете? Вы не можете сравнить std::vector < int, std::allocator > и std::vector < int, other_allocaltor >. При том что run-time аллокатор потребовал бы всего два указателя на функции и в итоге программы бы получалась компактнее и быстрее, потому что если вы напишете operator приведения к другому типу аллокатора у вас сгенерируется очень много лишнего кода.

А еще почему-то нет отдельной реализации для типов, удовлетворяющих std::is_trivial, где использовался бы realloc вместо аллокации + копирования / перемещения + освобождения старой памяти.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

А еще почему-то нет отдельной реализации для типов, удовлетворяющих std::is_trivial, где использовался бы realloc вместо аллокации + копирования / перемещения + освобождения старой памяти.

Потому что стандартная библиотека не обязана закрывать все возможные случаи в жизни. Подобного эффекта для случаев, когда вы работаете с POD-типами, вы можете достичь, например, с folly::fbvector

eao197 ★★★★★
()
Ответ на: комментарий от zx_gamer

Ты застрял в своем анси с++ и страдаешь, уже говорили про range, там как раз тот сахар, который сократит твой find(). А ещё ты пропустил контейнеры с полиморфными аллокаторами, и таки можно будет сравнить два разных вектора. Какие-то костыли делаешь вместо готовых constexpr функций. Плюсы действительно стали удобнее, перестань бодаться

kvpfs_2
()
Ответ на: комментарий от eao197

Тогда как вы в C++ собираетесь поддерживать подобную ковариантность возвращаемых по значению итераторов?

template < class T >
class ICollection
{
public:
   virtual Iterator < T > get_common_begin();
   virtual Iterator < T > get_common_end();
};

template < class T >
class Vector : public ICollection
{
public:
   Iterator < T > get_common_begin();
   Iterator < T > get_common_end();

   RandomAccessIterator < T > get_ra_begin();
   RandomAccessIterator < T > get_ra_end();
};

Поскольку мы говорим про итераторы, то меня интересует узнать равны ли итераторы друг другу.

Ну в теории можно: написать operator==, который сравнит адреса коллекций и адреса элементов этих коллекций.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от kvpfs_2

А ещё ты пропустил контейнеры с полиморфными аллокаторами, и таки можно будет сравнить два разных вектора.

Это те самые, которые реализуют operator std::vector < T, OtherAllocator > и генерируют (n*(n-1)) функций, где n — количество используемых типов для контейнера? Т.е. для 10 типов в std::vector эти самые полиморфные аллокаторы сгенерируют 90 функций приведения аллокаторов.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

Ваш пример кода не соответствует тому, с чем вы согласились выше. Глядя же на get_common_begin/get_ra_begin() хочется порадоваться, что STL в C++ проектировали не вы.

Интересно, а как вы решите проблему того, что Iterator<T> для Vector может иметь совсем другое представление, чем Iterator<T> для OrderedSet?

Ну в теории можно: написать operator==, который сравнит адреса коллекций и адреса элементов этих коллекций.

Т.е. это нормально когда мы сравниваем Iterator от Vector с Iterator от Set? И при сравнении пары итераторов мы вынуждены делать два сравнения вместо одного?

eao197 ★★★★★
()
Ответ на: комментарий от eao197

А нечего, что коллекции с тривиальными типами это чуть ли не самые частые типы? При том, что STL вообще менять не надо (хоть и его использовать нельзя), надо просто сделать enable_if, is_trivial реализацию, например для вектора, которая будет в operator= и конструкторе копирования использовать realloc.

Зато вот std::vector<bool> оптимизировать это очень нужно.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

А нечего, что коллекции с тривиальными типами это чуть ли не самые частые типы?

Отучаемся говорить за всех.

При том, что STL вообще менять не надо (хоть и его использовать нельзя), надо просто сделать enable_if, is_trivial реализацию, например для вектора, которая будет в operator= и конструкторе копирования использовать realloc.

Вы пропозал в комитет уже написали?

Зато вот std::vector оптимизировать это очень нужно.

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

Но, что характерно, итераторы отлично работают и для std::vector<bool>.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

Т.е. это нормально когда мы сравниваем Iterator от Vector с Iterator от Set? И при сравнении пары итераторов мы вынуждены делать два сравнения вместо одного?

Да, это нормально, потому что мы получаем возможность работать с абстрактными итераторами (т.е. функции будут работать с любыми коллекциями и не будет возникать такой проблемы, когда у меня есть массив, а функция хочет список и я занимаюсь перекладыванием байт). Что же касается двух сравнений — это мелочь.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

Правильно ли я понимаю, что ответа на вот это:

Интересно, а как вы решите проблему того, что Iterator для Vector может иметь совсем другое представление, чем Iterator для OrderedSet?

у вас просто нет?

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

На шаблонах C++ вы получаете именно это. Но, плюс к тому, в большинстве случаев вы получите по рукам от компилятора если вдруг попытаетесь сравнить vector<T>::iterator с set<T>::iterator, что в 100% есть ошибка.

Это, наверное, какой-то модный и молодежный стиль – отказаться от проверок в compile-time. Да еще где? В C++, где отстрелить себе конечности можно просто по недосмотру.

Что же касается двух сравнений — это мелочь.

void do_something(Iterator<MyType> from, Iterator<MyType> to) {
  for(; from != to; ++from) {
    ... // Какие-то действия.
  }
}

Т.е. на цикле внутри do_something мы получаем 2N сравнений вместо N, и это мелочь?

Если вы такими мелочами разбрасываетесь, то непонятно что вы хотите выиграть от compile-time.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

На шаблонах C++ вы получаете именно это.

Вот именно. Вы мне предлагаете весь код шаблонным сделать?

Т.е. на цикле внутри do_something мы получаем 2N сравнений вместо N, и это мелочь?

Да, это мелочь. Сравнение это одна инструкция процессора. И если уж на то пошло, то в языке есть заметно более накладные моменты.

zx_gamer ★★★
() автор топика
Ответ на: комментарий от zx_gamer

Вот именно.

«Вот именно» - это нежелание (или невозможность) отвечать на заданные вам вопросы?

Вы мне предлагаете весь код шаблонным сделать?

Если вам нужна скорость, то да.

Да, это мелочь.

Ну, OK.

eao197 ★★★★★
()
Ответ на: комментарий от zx_gamer

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

А с шаблонными контейнерами типо не написать абстрактно? Ты же адепт родственно-виртуальных штук, напиши обертку - абстрактный итератор.

kvpfs_2
()
Ответ на: комментарий от eao197

Вы мне предлагаете весь код шаблонным сделать?

Если вам нужна скорость, то да.

Я таки скромно хочу заметить что далеко не все расходы связаны с тем что код не шаблонный;-)

Бывают же накладные расходы на доступ в память (см. roofline model), накладные расходы на синхронизацию, плохое предсказание ветвлений и т.д. и т.п. Тут никакие шаблоны не помогут…

AntonI ★★★★★
()
Ответ на: комментарий от zx_gamer

Хорошо, а если тип нетривиальный? Например, имеем коллекцию тупых пар ключ-значение:

struct Pair {
    int key;
    int value;
};

В рамках современного C++ поиск по ключу можно было бы сделать тупо так:

std::vector<Pair> vec;

for (Pair& p : vec) {
    if (p.key == key) {
        std::cout << p.value << '\n';
        break;
    }
}

или так:

auto it = std::ranges::find_if(vec, [key](Pair &p) { return p.key == key; });

if (it != vec.end()) {
    return it->value;
}

Выглядит, может быть, страшновато, но сгенерированный код будет практически тем же самым. Притом тут мог быть не вектор, а, например, односвязный список, и ничего бы не изменилось.

Так вот, какой API для поиска вы предлагаете для коллекций в рамках парадигмы ООП и ANSI C++? Предположим, есть Vector<Pair> и LinkedList<Pair>. А если получу родительский интерфейс ICollection<Pair>?

Реализация мне даже не так интересна, как именно API. Мы же ради красоты кода от STL отказываемся.

static_lab ★★★★★
()
Ответ на: комментарий от AntonI

Я таки скромно хочу заметить что далеко не все расходы связаны с тем что код не шаблонный;-)

Так здесь же контекст разговора более чем определен: мы либо имеем разные и независимые классы итераторов (и, как следствие, вынуждены использовать шаблоны для обобщенного кода), либо у нас абстрактные интерфейсы с виртуальными методами.

В этой дихотомии виртуальные методы будут добавлять накладных расходов к тому, что вы перечислили.

Где-то компилятор сможет выполнить девиртуализацию, но далеко не везде.

eao197 ★★★★★
()