Pull to refresh
51
0
Алексей Егоров @PkXwmpgN

Пользователь

Еще вариант посмотреть на средства для алиасинга символов на стороне конкретного компилятора. Эти штуки пока не стандартизированы, но есть предложения, например, https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2729.htm

extern "C" int foo(int a) {
    return a;
}

#ifdef _WIN32
#pragma comment(linker, "/alternatename:bar=foo")
int bar(int);
#else
int bar(int a) __attribute__((alias("foo")));
#endif

int main() {
    foo(1);
    bar(1);
    assert(&foo == &bar);
}

Но там есть свои нюансы. В С++ на GCC/Clang нужно будет указывать mangled имя https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html.

https://godbolt.org/z/sGPY3PdYM

И если бы я был интервьювером, то с моей точки зрения решение из двух строчек с std::forward_list::splice_after() и std::forward_list::sort() было бы самым правильным

Возможно, у того человека который проводил интервью у автора, это тоже было бы самым правильным. Проблема скорее всего в том, что автор не знаком со стандартной библиотекой в достаточной степени, чтобы ее применить в такой ситуации. Я думаю интервьюер был бы не против если бы автор предложил ему несколько решений с использованием стандартной библиотеки, просто чтобы выразить суть алгоритмов (например forward_list::sort + forward_list::sort + forward_list::merge или forward_list::splice_after + forward_list::sort), объяснил как это работает, что быстрее, какие ограничения накладывает односвязный список и попытался часть из этого реализовать. Начать можно было просто с сортировки пузырьком, а дальше предложить развитие, ни где же не требуется сложность изначально. Но, возможно, я ошибаюсь на счет интервьюера )

А с ним не нужно "работать", его нужно просто отсортировать (ну присоединить один к другому, а потом отсортировать), продемонстрировав знание элементарных алгоритмов, сложности, декомпозиции и т.д. и, например, решить задачу за O(n*log(n)), а не за O(n^2), порассуждать. Полноценная абстракция односвязного списка с шаблонами, аллокаторами, операциями, ни как не поможет в решение задачи, если человек не знает как отсортировать список, возможность вызвать метод insert вместо "ручного" присваивания одного указателя, не приблизит его к решению. Ну т.е. в чем смысл писать метод insert для задачи сортировки списка, если ты не знаешь как сортировать список... Напротив, это только покажет, что человек просто не понимает смысла задачи и самое главное зачем он ее решает. Все нужные операции, обобщения, оптимизации, баги ) можно добавить следующими итерациями, отталкиваясь от исходного решения, алгоритма.

unique_ptr только в некоторых случаях на некоторых платформах и АБИ будет иметь маленький оверхед

https://youtu.be/rHIkrotSwcc?t=1058

Я встречал немного другой взгляд на требование C/C++. Все что перечислено в статье шаблоны, алгоритмы, итераторы, ренжи... - это все высокоуровневые абстракции. Помимо этого важно понимать, как из этих высокоуровневых абстракций в итоге получается машинный код. Как они реализованы и чего стоят. Возможно это кого-то удивит, но очень много программистов, у которых в резюме написан опыт 3+ лет на C++, не знают низкоуровневых вещей, например, многих ставит в тупик вопрос про выравнивание данных (здесь конечно можно заметить что "настоящему" программисту на С++, который пишет на "хорошем", "декларативном" С++ эти знания ни к чему - это тот же холивар, что и про алгоритмы). При этом я не встречал ни одного программиста на С, у которого подобные вопросы вызывали бы недоумение. Поэтому иногда когда пишут про C/C++ имеют ввиду знание как раз вот этого низкоуровневого наследия С, на котором построен C++. Но такой взгляд встречается редко, здесь вы правы скорее всего.

Это не то что реально происходит, а лишь какая то демонстрация непонятная

Хорошо, не нравиться cppinsight, давайте посмотрим на AST или трансляция кода это тоже не то, что реально происходит?

Вот для вашего примера
https://godbolt.org/z/3f1rMWG4e

А вот для обычного массива
https://godbolt.org/z/4T6hGoKnW

И во вторых, если в ЯЗЫКЕ нет массива, это не значит что РЕАЛИЗАЦИЯ(например от msvc) не сможет его сделать на built-in'ах, при этом ничего не потеряв вы убрали лишнюю вещь из языка.

Это также не значит что реализация СМОЖЕТ это сделать.

Но если предположить что смогла. Вот я могу взять clang и использовать стандартную библиотеку gcc или наоборот. По libc++, например, https://libcxx.llvm.org/#platform-and-compiler-support. Как это должно работать? Вы предлагает стандартизировать built-in'ы?

Никаким языковым способом не реализуемо на данный момент сделать их constexpr.

Это реализуется за счет union.

Заменить это всё можно одним фундаментальным типом byte и указателями, действительно: с помощью byte и системы типов С++ можно создавать любые типы, в том числе аналогичные int, double, float, bool и т.д. из фундаментального набора. Тут мы убиваем сразу несколько зайцев - нет больше исключений для фундаментальных типов в разрешении перегрузки, нет исключений в шаблонном коде для наследования( от фундаментальных типов нельзя наследоваться) ну и другие более мелкие исключения для подобных типов уходят в прошлое

И одним из этих убитых "зайцев" будет видимо constexpr.

Хорошо. Можете, пожалуйста, на https://godbolt.org привести код инициализации и использования вашего union_t в constexpr-контексте. Не важно каким компилятором и стандартом вы это сделаете?

Как массив байт с правильным алигментом, прямо в статье код делающий всё что вам нужно

И было бы здорово посмотреть, думаю не только мне, как вы с помощью массива байт реализуется ваш fucking::optional<T> как literal type, что требует стандарт.

Следующий код полностью заменяет юнион, не имеет никакого оверхеда относительно него и имеет более понятный пользователю интерфейс (emplace / destroy)

Как вы лихо запихали placement new и reinterpret_cast в constexpr-контекcт. Можете продемонстрировать как вы замените вот такой код с помощью вашего union_t?

struct T {
    union {
        int n;
        float f;
    };
    constexpr T(int a) : n(a) {}
    constexpr T(float b) : f(b) {}  
};
int main() {
    constepxr T a = 1;
    constexpr T b = 0.5f;
    static_assert(a.n == 1);
    static_assert(b.f == 0.5f);
}

И реальный кейс: как вы планируете реализовать стандартный std::optional без union или std::variant?

Как же их заменить? Рекусивным(или через множественное наследование) туплом с элементами одного типа(да, это было очевидно)))

Вы же это несерьезно, правда?
https://cppinsights.io/s/62550a23 и вот это у вас будет в каждом юните трансляции?

правда войнушку даже он не ждал

"Войнушка" - это когда дети во дворе друг за другом с палками бегают.

P. S. Кстати, нам по-прежнему нужны талантливые программисты. Список актуальных вакансий по ссылке. (https://hh.ru/employer/2523?dpt=2523-2523-mvideo#it)

Вакансии

Видимо вы ищите талантливых "тыжпрограммистов".

Поскольку C++ очень близок к металлу

К тяжелому?

Если кому понравился мой корпус и статья, можете поставить мне лайк в Инстаграм

Корпус понравился, получилось здорово. У меня нет инстаграма, к счастью, перешел по ссылке посмотреть может там есть какие-то еще DIY-фото, но по итогу не очень понятно как ваш интаграм связан со статьей, плюс вы мне просто показываете фак и посылайте, прошу прощения, на х*й.

Инстаграм

Но возможно, я просто что-то не понимаю в маркетинге или как там это правильно называется.

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

Приведенный фрагмент кода не аналогичен. Transmission использует Glib и g_free соответственно. Ни один компилятор не посчитает бессмысленым вызов memset и не уберет его перед вызовом g_free.

code
#include <stdlib.h>
#include <string.h>
#include <glib.h>

typedef void* gpointer;
typedef struct
{
    char* target;
    gpointer thereWereOtherElementsButTheyWereTruncatedForAnExample;
    gpointer builder;
}
MakeMetaUI;

void doSomething(gpointer);
void freeMetaUI(gpointer p)
{
    MakeMetaUI* ui = static_cast<MakeMetaUI*>(p);

    doSomething(ui->builder);
    g_free(ui->target);
    memset(ui, ~0, sizeof(MakeMetaUI));
    g_free(ui);
    
}
g++ `pkg-config --cflags --libs glib-2.0` test.cpp -O2 -masm=intel -S
output
	.file	"test.cpp"
	.intel_syntax noprefix
	.text
	.p2align 4
	.globl	_Z10freeMetaUIPv
	.type	_Z10freeMetaUIPv, @function
_Z10freeMetaUIPv:
.LFB334:
	.cfi_startproc
	endbr64
	push	rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	mov	rbp, rdi
	mov	rdi, QWORD PTR 16[rdi]
	call	_Z11doSomethingPv@PLT
	mov	rdi, QWORD PTR 0[rbp]
	call	g_free@PLT
	mov	QWORD PTR 16[rbp], -1
	pcmpeqd	xmm0, xmm0
	mov	rdi, rbp
	movups	XMMWORD PTR 0[rbp], xmm0
	pop	rbp
	.cfi_def_cfa_offset 8
	jmp	g_free@PLT
	.cfi_endproc
.LFE334:
	.size	_Z10freeMetaUIPv, .-_Z10freeMetaUIPv
	.ident	"GCC: (Ubuntu 10.3.0-1ubuntu1~20.04) 10.3.0"
	.section	.note.GNU-stack,"",@progbits
	.section	.note.gnu.property,"a"
	.align 8
	.long	 1f - 0f
	.long	 4f - 1f
	.long	 5
0:
	.string	 "GNU"
1:
	.align 8
	.long	 0xc0000002
	.long	 3f - 2f
2:
	.long	 0x3
3:
	.align 8
4:
clang++ `pkg-config --cflags --libs glib-2.0` test.cpp -O2 -masm=intel -S
output
	.text
	.intel_syntax noprefix
	.file	"test.cpp"
	.globl	_Z10freeMetaUIPv                # -- Begin function _Z10freeMetaUIPv
	.p2align	4, 0x90
	.type	_Z10freeMetaUIPv,@function
_Z10freeMetaUIPv:                       # @_Z10freeMetaUIPv
	.cfi_startproc
# %bb.0:
	push	rbx
	.cfi_def_cfa_offset 16
	.cfi_offset rbx, -16
	mov	rbx, rdi
	mov	rdi, qword ptr [rdi + 16]
	call	_Z11doSomethingPv
	mov	rdi, qword ptr [rbx]
	call	g_free
	pcmpeqd	xmm0, xmm0
	movdqu	xmmword ptr [rbx], xmm0
	mov	qword ptr [rbx + 16], -1
	mov	rdi, rbx
	pop	rbx
	.cfi_def_cfa_offset 8
	jmp	g_free                          # TAILCALL
.Lfunc_end0:
	.size	_Z10freeMetaUIPv, .Lfunc_end0-_Z10freeMetaUIPv
	.cfi_endproc
                                        # -- End function
	.ident	"Ubuntu clang version 11.1.0-++20210428103817+1fdec59bffc1-1~exp1~20210428204431.166"
	.section	".note.GNU-stack","",@progbits
	.addrsig

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

Branch condition evaluates to a garbage value

и дальше с этим ничего не делается, а просто считается false (имеет право, потому что UB). Судя по всему в версия 4.0.1 и еще в 5.0.0 с этим были баги

https://godbolt.org/z/EPd37cjfW

<source>:4:38: warning: Branch condition evaluates to a garbage value
int check_compiler() { int i; return i || !i; }
                                     ^
<source>:5:39: warning: Branch condition evaluates to a garbage value
int check_compiler1() { int i; return !i || i; }
                                      ^~
<source>:10:7: warning: Branch condition evaluates to a garbage value
  if (x) // expected-warning{{Branch condition evaluates to a garbage value}}
      ^
<source>:17:10: warning: Branch condition evaluates to a garbage value
  return x ? 1 : 0; // expected-warning{{Branch condition evaluates to a garbage value}}
         ^
4 warnings generated.

Я бы еще предложим немного расширить интерфейс обработки ошибок Accept'а (для консистентности).


Сейчас чтобы пользоваться функцией нужно:


  1. Знать с чем сравнивать (и как сравнивать) возвращаемое значение;
  2. Знать как в случае ошибки правильно вызывать коллбек, какие данные туда передавать, какие данные ожидают на той стороне.

Как-нибудь так:


const auto new_descriptor = Accept(listener, kAcceptTimeout);
if(is_invalid_descriptor(new_descriptor))
{
    auto data = ProcessError(new_descriptor);
    return on_data(data.descriptor, data.payload);
}

В самом предложении http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0479r0.html есть несколько тестов

Вот, например, GCC генерирует код с одиночным if'ом таким образом, что мы прыгаем только если у нас ошибка (условие выполнено)
https://godbolt.org/z/MET3vq


Мы можем явно сделать if "горячим" пометив его атрибутом [[likely]] и мы будем прыгать тогда когда ошибки нет (условие не выполнено), тогда год будет таким:
https://godbolt.org/z/51c6oT

Можно попробовать вместо итерирования по индексам, получения элемента кортежа по индексу, преобразования его в вариант и последующем посещением варианта, сразу посещать кортеж. Например, как-нибудь, так:


namespace details
{
    template<typename Tuple, typename Func, size_t... I>
    void visit(Tuple&& t, Func&& f, std::index_sequence<I...>)
    {
        (..., std::forward<Func>(f)(std::get<I>(std::forward<Tuple>(t))));
    }
}

template<typename Tuple, typename Func>
void visit(Tuple&& t, Func&& f)
{
     details::visit(std::forward<Tuple>(t), std::forward<Func>(f),
        std::make_index_sequence<std::tuple_size_v<std::decay_t<Tuple>>>());
}
...
auto tuple = std::make_tuple(1, "5"s, 'c', 4.5f);
visit(tuple, [](auto& value) { std::cout << value << std::endl; });

Несложно добавить фильтрацию по индексам или другим критериям или генерировать отфильтрованный кортеж.


Как известно, кортеж представляет из себя гетерогенный список

Это не просто список, это список фиксированного размера, т.е. кортеж это обобщение std::pair, а не std::list. Т.е. непонятно зачем превращать кортеж и работать с ним как со списком std::variant. Можно тогда сразу использовать std::array<std::variant<...>, N>;

Information

Rating
Does not participate
Location
Санкт-Петербург, Санкт-Петербург и область, Россия
Registered
Activity