LINUX.ORG.RU

std::any без аллокаций

 ,


0

2

Я тут не нашёл ни в STL, ни в Boost версии any позволяющей избегать аллокаций. Точнее в STL есть small value optimization, но на практике размеры буфера очень малы (на GCC это всего лишь 1 указатель, в MSVC 3 указателя), а возможности задать свой размер нет (например, допустим, мы знаем, что большинство объектов меньше 64 байт).

Так что я сделал свой Any: https://pastebin.com/JMS32LNi

Вроде всё хорошо работает, кроме некоторых конструкторов:

int x = 10;
Any<64> v0 = x; // Ошибка

Any<64> v1 = 10; // Работает
Any<128> v2 = v1; // Ошибка

Собственно, в обоих случаях проблема в том, что вызывается конструктор template<typename T> Any(T &&value) вместо template<typename T> Any(const T &value) для первого случая и template<std::size_t N> Any(const Any<N> &value) для второго.

Как можно исправить вызовы конструкторов?

P. S. Такая же проблема с оператором присваивания - всегда выбирается версия с аргументом T, а версии с Any игнорируются.

★★★★★

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

Как можно исправить вызовы конструкторов?

Вроде во всяких букварях типа от Меерса написано.

utf8nowhere ★★★
()

А точно нужен именно std::any, а не std::variant? Для высоконагруженного кода variant предпочтительнее, а для всего остального лишние аллокации обычно не так критичны

annulen ★★★★★
()

В лучших традициях int&& != template<typename T = int> T&&: первая — это rvalue-reference, а второе — universal reference.

Опытные ходители по граблям советуют https://stackoverflow.com/questions/7863603/how-to-make-template-rvalue-refer...

P. S. кстати, gcc ругается, что new пытается создать reference type в строке 86, что можно исправить new (...) typename std::remove_reference<T>::type(std::move(value))

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

Кстати, есть посмотреть на std::any — там тоже только конструктор с универсальной ссылкой. Как бы не было противно, лучше ознакомиться с тем, как это сделано в стандартной библиотеке.

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

«Первое — это фрукт, а второе — яблоко»?

Ах если бы все было так просто.

#include <stdio.h>

#define D(type) template<typename T> void foo(type) { puts("tmpl :: " #type); }

D(const T&);
D(T&&);

#define I(type) void bar(type) { puts(#type); }

I(const int&);
I(int&&);

int main()
{
	int x = 5;
	foo(5); // tmpl :: T&&
	foo(x); // tmpl :: T&&
	bar(5); // int&&
	bar(x); // const int&
}
kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

Надо было Страуструпу под universal reference какую-нибудь отдельную закорючку придумать. Например, &&&. :)

pr849
()

Собственно, в обоих случаях проблема в том, что вызывается конструктор template Any(T &&value) вместо template Any(const T &value)

Логично, шаблон с универсальной ссылкой генерит более специализированную версию, которая и вызывается. Нужно либо писать constraint для универсальной версии, либо генерить SFINAE ошибку (если цпп20 нет).

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

Ну вот я сделал типа такого:

template<typename T> Any(T &&value) noexcept requires std::is_rvalue_reference_v<T> {
	using TT = typename std::decay<T>::type;
	if constexpr (sizeof(TT) > sizeof(m_storage) || alignof(TT) > alignof(std::size_t)) {
		m_heapStorage = new TT(std::forward<T>(value));
	} else {
		new (m_storage) TT(std::forward<T>(value));
	}
	m_operations = &OPERATIONS<T>;
}	

И теперь вместо этого конструктора вызывается copy constructor, даже когда я передаю не Any.

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

В стандартной библиотеке проще ситуация. У них там any нешаблонный тип. Соответственно, они могут легко написать реализацию конструкции any из any и any из любого другого типа. А мне непонятно как сделав универсальный конструктор различать внутри него экземпляры моего Any (специализированные с разными размерами буферов) и всё остальное.

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

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

А мне непонятно как сделав универсальный конструктор различать внутри него экземпляры моего Any (специализированные с разными размерами буферов) и всё остальное.

Отнаследовать Any<> от Any_mark?

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

template<typename T> Any(T &&value) noexcept requires std::is_rvalue_reference_v<T>

И такое условие сработает ровно никогда.

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

На 136й строчке, ты вызываешь new, а можно алоцировать из memory_resource. Это иногда более хорошая альтернатива для any чем буфер внутри.

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

На 136й строчке, ты вызываешь new

Я?!

а можно алоцировать из memory_resource

Так это нужно более общую поддержку аллокаторов пилить, а не pmr.

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

Это чтобы буфер был выровнен на размер указателя. Так как такое выравнивание подходит большинству объектов.

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

Так это нужно более общую поддержку аллокаторов пилить, а не pmr.

Ну хотя для аллокаторов придётся делать класс шаблонным, а memory_resource этого не треба.

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

Ну да, фишка any как раз ведь а том, чтобы можно было какой нибудь объект без шаблонов передавать. Если уже на шаблонах, то в нем смысла и нет.

zerhud
()

может быть в твоем случае вообще лучше использовать concepts? Поскольку там можно смело проверять размер типа на этапе компиляции.

safocl ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.