Задать вопрос
  • C1202 recursive type or function dependency context too complex. В чем причина?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Судя по коду GetDeterminant() имеет разное поведение для размерности 2 и для остальных размерностей.
    Я не буду вдаваться в детали приведенного кода (если честно, там гора ошибок), просто перефразирую его так, чтобы C1202 больше не появлялась.

    // Deduction branch. Common determinant calculation.
    template< typename TStorage, size_t DIMENSION >
    struct DeterminantCalculator final
    {
    	// Or implement the private instantiation for `DeterminantCalculator<TStorage, 1>`.
    	static_assert( DIMENSION > 2, "Dimension of matrix should be >= 2." );
    	
    	static inline const TStorage Calculate( const MatrixNxN<TStorage, DIMENSION>& matrix )
    	{
    		TStorage result = 0;
    		for( size_t index = 0; index < DIMENSION; ++index )
    		{
    			// ((index & 0)? 1 : -1) --> always -1.
    			// matrix.GetMinor( 0, 0 ) --> uninitialized result.
    			// matrix.GetMinor( DIMENSION - 1, 0 ) --> garbage result due to design of `GetMinor`.
    			result += matrix[ index ] * ((index & 0)? 1 : -1) * matrix.GetMinor( index, 0 ).GetDeterminant();
    		}
    		
    		return result;
    	}
    };
    
    // Terminal branch. Determinant for 2x2 matrix.
    template< typename TStorage >
    struct DeterminantCalculator<TStorage, 2> final
    {
    	static inline const TStorage Calculate( const MatrixNxN<TStorage, 2>& matrix )
    	{
    		return matrix[ 0 ] * matrix[ 3 ] - matrix[ 2 ] * matrix[ 1 ];
    	}
    };
    
    template<typename _Type, size_t _Size>
    _Type MatrixNxN<_Type, _Size>::GetDeterminant()
    {
    	return DeterminantCalculator<_Type, _Size>::Calculate( *this );
    }
    
    template<typename _Type, size_t _Size>
    MatrixNxN<_Type, _Size - 1> MatrixNxN<_Type, _Size>::GetMinor(size_t row, size_t col)
    {
    	size_t index = 0;
    	MatrixNxN<_Type, _Size - 1> result;
    	
    	// Should be `i < _Size - 1`, since the `result` is `MatrixNxN<_Type, _Size - 1>`.
    	for (size_t i = 0; i < _Size; i++)
    	{
    		if (i == col)
    		{
    			// Uninitialized `result` in case of `col == 0`. E.g. ALWAYS.
    			continue;
    		}
    		
    		// Should be `i < _Size - 1`, since the `result` is `MatrixNxN<_Type, _Size - 1>`.
    		for (size_t j = 0; j < _Size; j++)
    		{
    			if (j == row)
    			{
    				// Uninitialized `result` in case of `row == 0`.
    				continue;
    			}
    
    			result[index++] = GetElement(j, i);
    		}
    	}
    	return result;
    }
    Ответ написан
  • Откуда берется overhead?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Стандарт C++ предписывает статическое расположение для глобальных и статических переменных. Это означает целую массу побочных эффектов для генерации использующего переменную кода.

    Для примера можно взять твою функцию swap и посмотреть на то, как компилятор будет генерировать код даже с учетом оптимизации.
    Смотрим первый пример
    void swap( int& left, int& right )
    {
        static int t = left;
        left = right;
        right = t;
    }

    И вот результат обработки clang.
    swap(int&, int&):                            # @swap(int&, int&)
            pushq   %r14
            pushq   %rbx
            pushq   %rax
            movq    %rsi, %rbx
            movq    %rdi, %r14
            movb    guard variable for swap(int&, int&)::t(%rip), %al
            testb   %al, %al
            je      .LBB0_1
    .LBB0_3:
            movl    (%rbx), %eax
            movl    %eax, (%r14)
            movl    swap(int&, int&)::t(%rip), %eax
            movl    %eax, (%rbx)
            addq    $8, %rsp
            popq    %rbx
            popq    %r14
            retq
    .LBB0_1:
            movl    $guard variable for swap(int&, int&)::t, %edi
            callq   __cxa_guard_acquire
            testl   %eax, %eax
            je      .LBB0_3
            movl    (%r14), %eax
            movl    %eax, swap(int&, int&)::t(%rip)
            movl    $guard variable for swap(int&, int&)::t, %edi
            callq   __cxa_guard_release
            jmp     .LBB0_3
    Смотрим второй пример
    void swap( int& left, int& right )
    {
        int t = left;
        left = right;
        right = t;
    }

    Вот результат выдачи от clang.
    swap(int&, int&):                            # @swap(int&, int&)
            movl    (%rdi), %eax
            movl    (%rsi), %ecx
            movl    %ecx, (%rdi)
            movl    %eax, (%rsi)
            retq


    Не обязательно быть специалистом в ассемблере чтобы увидеть прямую разницу в сгенерированном коде.
    На самом деле, если t объявить именно как глобальную переменную, то сгенерированный код станет немного легче, но он все равно будет объемнее и тяжелее кода с локальной переменной. Уже просто потому что глобальная переменная находится в глобальной памяти, а запросы из процессора в ОЗУ - сравнительно долгая операция.
    Сравнение скорости доступа к памяти
    cacheanim.gif
    Ответ написан
    1 комментарий
  • Поможете найти ошибку в коде?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Если смотреть по код сверху вниз.

    Первая ошибка:
    this->value = NULL;
    В общем смысле ты не можешь знать, каким типом параметризуют твой шаблон. Но делая присвоение NULL ты надеешься на то что это будет указатель. Далее по тексту ты сам же свою надежду и разбиваешь, инстанцируя свой шаблон от типа int.
    Если тебе надо присвоить переменной значение по умолчанию - используй конструктор по умолчанию.
    Например так.
    this->value = T1(); // Или this->value = T1{}; для C++11 и выше.

    this в этом месте, кстати, можно смело опустить.

    Вторая ошибка:
    Type(T1 value) : TypeSize(value)
    // ...
    Type() : TypeSize()

    Для обращения к сущностям родительского шаблона нужно писать полную квалификацию имени сущности.
    Type(T1 value) : TypeSize<T1>::TypeSize(value)
    // ...
    Type() : TypeSize<T1>::TypeSize()


    Если заменить все this->value на value, то компилятор снова скажет про обращение к несуществующему полю. Но если к value обращаться используя его полную квалификацию TypeSize<T1>::value, код соберется должным образом.

    Третья ошибка:
    Type(T1 value) : TypeSize(value)
    {
    	this->value = value;
    }
    Type() : TypeSize()
    {
    	this->value = 0;
    }

    Ты и инициализацию родительского класса производишь, и значение меняешь. Это избыточно. Инициализацию value из этих конструкторов лучше убрать и предоставить все права по инициализации своих полей типу TypeSize. Плюс, конструктор по умолчанию у тебя снова надеется на простой тривиальный тип. Присвоение нулю приведет к ошибке компиляции уже при инстанцировании для тривиального составного типа.
    Лучше так:
    Type(T1 value) : TypeSize<T1>::TypeSize(value)
    {
    }
    Type() : TypeSize<T1>::TypeSize()
    {
    }
    Ответ написан
    2 комментария
  • Как можно сделать специализацию класса для нескольких типов?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Предположим, есть общий шаблон сериализации данных для работы с абстрактными потоками ввода-вывода.
    Обобщенный шаблон такой сущности может выглядеть вот так.
    template< typename TValueType >
    struct Serializer final
    {
    	static inline const bool Read( const BasicStream& stream, TValueType& value_storage );
    
    	static inline const bool Write( BasicStream& stream, const TValueType& value_storage );
    };


    У этого шаблона может присутствовать набор частных инстанцирований для простых и стандартных типов.
    Пример
    template<>
    struct Serializer<int8_t> final
    {
    	static inline const bool Read( const BasicStream& stream, int8_t& value_storage );
    
    	static inline const bool Write( BasicStream& stream, const int8_t& value_storage );
    };
    
    // ...
    
    template<>
    struct Serializer<float> final
    {
    	static inline const bool Read( const BasicStream& stream, float& value_storage );
    
    	static inline const bool Write( BasicStream& stream, const float& value_storage );
    };


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

    А теперь вкусненькое. Среди стандартных типов есть и std::string, и std::vector, и std::map. Если пользователь решит сериализовать весь контейнер своих пользовательских типов, нам не стоит обязывать его писать частное инстанцирование сериализатора для контейнера. Нам надо предусмотреть сериализацию любого стандартного контейнера с любым пользовательским типом.

    Мы это делаем с помощью частичной специализации шаблона сериализатора.
    Для произвольной строки
    template< typename TCharType, typename TCharTraits, typename TAllocator >
    struct Serializer<std::basic_string<TCharType, TCharTraits, TAllocator>> final
    {
    	static inline const bool Read( const BasicStream& stream, std::basic_string<TCharType, TCharTraits, TAllocator>& value_storage );
    
    	static inline const bool Write( BasicStream& stream, const std::basic_string<TCharType, TCharTraits, TAllocator>& value_storage );
    };
    Для std::vector
    template< typename TElement, typename TAllocator >
    struct Serializer<std::vector<TElement, TAllocator>> final
    {
    	static inline const bool Read( const BasicStream& stream, std::vector<TElement, TAllocator>& value_storage );
    
    	static inline const bool Write( BasicStream& stream, const std::vector<TElement, TAllocator>& value_storage );
    };
    Для std::pair
    template< typename TLeftElement, typename TRightElement >
    struct Serializer<std::pair<TLeftElement, TRightElement>> final
    {
    	static inline const bool Read( const BasicStream& stream, std::pair<TLeftElement, TRightElement>& value_storage );
    
    	static inline const bool Write( BasicStream& stream, const std::pair<TLeftElement, TRightElement>& value_storage );
    };


    И так далее.

    А вот как может выглядеть внешнее определение функции при частичной специализации.
    Пример чтения из стрима в вектор
    template< typename TElement, typename TAllocator >
    inline const bool Serializer<std::vector<TElement, TAllocator>>::Read(
    	const BasicStream& stream,
    	std::vector<TElement, TAllocator>& value_storage
    )
    {
    	size32_t stored_size;
    	Serializer<size32_t>::Read( stream, stored_size );
    
    	value_storage.resize( stored_size );
    	for( TElement& element : value_storage )
    	{
    		Serializer<TElement>::Read( stream, element );
    	}
    
    	return true;
    }
    Ответ написан
    8 комментариев
  • C++/CLI как располагаются методы?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Судя по всему, у тебя очень слабо с синтаксисом C++. В C++ есть два важных термина: объявление(declaration) и определение(definition).

    При объявлении сущности достаточно указать только ее внешний вид и опустить детали внутренних свойств.
    Определение сущности подчиняется ODR (One Definition Rule) - правилу одного определения.
    One Definition Rule гарантирует нам детерминизм любого используемого в коде типа, любой функции, любой переменной в рамках одной области видимости (но не в разных).
    Это правило позволяет сказать, что свойства программы однозначно определены до процесса трансляции и не изменяются после него.

    К примеру есть файл .cpp, в нем есть метод, который не относится не к какому пространству

    Это не метод. Это глобальная функция. Она объявлена в глобальном пространстве конкретного модуля трансляции. Это просто объявление чтобы функцией можно было пользоваться не нарушая ODR.

    Есть класс, он описан и тд, но я заметил в нем есть "вызова" методов, без всякого описания, то есть даже значения не передаются

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

    Как тебе быть для переноса этого добра в C#? Постарайся понять логику и выразить ее в терминах C#. :)
    Статические функции тебе и там тоже доступны. Но у тебя в C# нет разделения на объявление и определение.
    Ответ написан
    2 комментария
  • Сколько парадигм можно использовать одновременно при написании кода?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Так, ну поехали тогда примерно с начала.

    Парадигма программирования - это набор довольно строгих правил, определяющих способ организации вычислительных процессов программы.
    В целом, парадигма (para (греч.) - около; digma (греч.) - проба, образец) - это форма некоторой концепции, изложенная в виде формальных правил.

    Вот для примера несколько парадигм программирования, которые поддерживает язык C++:


    Реализация кода программы средствами современного C++ становится значительно легче, за счет усложнения самого кода, благодаря совместному использованию различных парадигм программирования. Часть кода незачем исполнять в ООП стиле, часть кода более удобна в виде шаблонов функций или классов, а часть кода незачем стараться делать вне ООП парадигмы.
    Ответ написан
    Комментировать
  • Как можно вычислить максимальное и минимальное значение массива?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Коротко, ты можешь не сохранять минимальное и максимальное значение отдельно, т.к. они всегда доступны по индексу в массиве. Другое дело - это что у тебя нет этих индексов до начала прохода по массиву, тебе их просто неоткуда взять. Этот момент и определяет верное начальное состояние в решении твоей задачи. Отсутствие этого момента и приводит к тому, что твой код сейчас работает неверно.

    Тебе нужны только индексы тех элементов, которые ты считаешь как минимальный и максимальный.
    size_t min_value_index = 0;
    size_t max_value_index = 0;

    Я выбираю инициализацию в 0 для того чтобы изначально обозначить первый же элемент массива и как минимальный, и как максимальный одновременно. Именно так я определю начальное состояние алгоритма.

    Далее надо сделать проход по массиву.
    for( size_t index = 1; index < stream_length; ++index )
    {
       // ...
    }

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

    Теперь надо записать условие выбора минимального и максимального значений.
    for( size_t index = 1; index < stream_length; ++index )
    {
       if( stream[ index ] < stream[ min_value_index ] )
       {
          min_value_index = index;
       }
       
       if( stream[ index ] > stream[ max_value_index ] )
       {
          max_value_index = index;
       }
    }

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

    По завершении цикла в min_value_index будет гарантированно лежать индекс минимального значения массива, а в max_value_index - индекс максимального.

    Как работает этот код.
    Ответ написан
    7 комментариев
  • Реализация конечного цикла без проверки условия выхода?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Если коротко, то в этом коде происходит дичь. :)
    Этот код нельзя компилировать, он в общем случае рванет.

    Опираться будем на (arg / N). Что это означает? Это означает просто целочисленное деление.
    Единица в результате этой операции получится только если arg будет эквивалентен N.

    Теперь обратим внимание на (int)CycleIteration. Опустим момент что писавший это человек любит "острые ощущения". Обратим внимание только на то, что тут адрес глобальной функции переводится в число.

    Далее обратим наше внимание на ((int)CycleEnd - (int)CycleIteration)). Тип int тут не спроста. Функции CycleEnd и CycleIteration могут быть расположены в произвольных местах бинарного образа. Тут определяется буквальное расстояние между двумя функциями в образе. И это расстояние может быть как положительным, так и отрицательным.

    А теперь собираем все вместе. Мы определяем расстояние между функциями итерации и конца и переключаемся с итерации на конец только тогда, когда счетчик итераций достигает лимита.
    ptr будет принимать значение CycleIteration до тех пор, пока arg < N, а когда arg == N, ptr принимает значение CycleEnd.
    Ответ написан
  • Как инициализировать проверяемый объект для google тестов?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Когда ты хочешь протестировать объект, ты пишешь для него тест.
    TEST( TestStaticString, GetChar )
    {
    	using TestString = Black::StaticString<'A', 'B', 'C'>;
    
    	EXPECT_EQ( 'A', TestString::GetChar( 0 ) );
    	EXPECT_EQ( 'B', TestString::GetChar( 1 ) );
    	EXPECT_EQ( 'C', TestString::GetChar( 2 ) );
    }


    Слово TEST - это макрос, раскрывающийся в специальную обвязку твоего теста. Его первый параметр - это имя тестового кейса, второй - имя теста.
    Инициализацию тестируемого объекта ты делаешь сам.
    Проверки теста производятся с помощью макросов EXPECT_* и ASSERT_*.
    Expect не останавливает тест в случае провала, а assert - останавливает. Таким образом можно разделять проверки в тесте на критические (после такой ошибки тест незачем продолжать) и обособленные (тест можно продолжать и получить множество обособленных ошибок).
    За деталями можно обратиться к примеру из самих гуглотестов.

    Твой класс VectorTest - это фикстура тестов. Применять ее к своим тестам ты можешь с помощью макроса TEST_F. Прочитать про это можно тут.
    Фикстуры нужны тогда, когда для группы твоих тестов запуск и остановка являются одинаковыми. В этом случае инициализацию и финализацию тестируемого объекта стоит делать в фикстуре.
    Ответ написан
    Комментировать
  • Зачем std::forward иметь две сигнатуры и явно указывать тип?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Думаю, вот эта статья даст ответы на твои вопросы.

    Если максимально точечно, то это раздел "Реализация идеальной передачи с использованием std::forward" и цитата в нем.
    Еще один момент, который нужно отметить: использование std::remove_reference. На самом деле forward может быть реализован и без использования этой функции. Сжатие ссылок выполнит всю работу, таким образом, применение std::remove_reference для этого избыточно. Однако, эта функция позволяет вывести T& t в ситуации, когда этот тип не может быть выведен (согласно стандарту С++, 14.8.2.5), поэтому необходимо явно указывать параметры шаблона при вызове std::forward.
    Ответ написан
    Комментировать
  • Как работает std::vector((NL + NE) * (NL + NE), -1.0).swap(cache)?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Функция swap() делает размен состояния между двумя контейнерами.
    В этой строке у тебя сперва создается вектор из N элементов равных -1, а потом его состояние разменивается с состоянием cache. При размене не происходит дополнительных выделений памяти, состояния векторов в буквальном смысле меняются местами.
    Далее исполнение выходит за область видимости временного контейнера, что приводит к его удалению. У нас на руках остается нужный нам cache с набором из -1.

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

    Тут справка по вектору.
    Там описан весь интерфейс вектора.
    Ответ написан
    Комментировать
  • Как реализовать фабричный метод без switch?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Фабричный метод - это т.н. виртуальный конструктор. Внутри функции не должно быть конструкций выбора. Сам фабричный метод передается в другое место для порождения объектов некоторого класса с определенным интерфейсом.

    Конструкции выбора обычно присутствуют внутри абстрактных фабрик. В фабрику приходит некий идентификатор типа и (если идентификатор определен) фабрика создает объект некоторого класса с определенным интерфейсом.

    Абстрактную фабрику можно реализовать на основе контейнера фабричных методов. Шаблоны C++ и стандарты C++11/14 нам в этом только помогут. Самый простой код такой фабрики может выглядеть вот так:
    Пример фабрики
    template< typename TInterface, typename... TArguments >
    class AbstractFactory final
    {
    public:
    	// Produce the implementation, but return the pointer to interface.
    	inline std::shared_ptr<TInterface> Produce( const std::string& implementation_name, TArguments... arguments )
    	{
    		auto found_function = m_factory_functions.find( implementation_name );
    		return ( found_function == m_factory_functions.end() )? std::shared_ptr<TInterface>{} : found_function->second( std::forward<TArguments>( arguments )... );
    	};
    	
    	// Define the implementation.
    	template< typename TImplementation >
    	inline const bool DefineImplementation()
    	{
    		return DefineImplementation<TImplementation>( TImplementation::ClassName() );
    	};
    	
    	// Define the implementation.
    	template< typename TImplementation >
    	inline const bool DefineImplementation( const std::string& implementation_name )
    	{
    		// Abort the incorrect registration.
    		static_assert( std::is_base_of<TInterface, TImplementation>::value, "Implementation may only be derived from interface of Factory." );
    		
    		auto found_function = m_factory_functions.find( implementation_name );
    		if( found_function == m_factory_functions.end() )
    		{
    			m_factory_functions[ implementation_name ] = &AbstractFactory<TInterface, TArguments...>::template ConstructImplementation<TImplementation>;
    			return true;
    		};
    		
    		return false;
    	};
    	
    	// Check the implementation name is already defined.
    	inline const bool IsImplementationDefined( const std::string& implementation_name ) const
    	{
    		return m_factory_functions.find( implementation_name ) != m_factory_functions.end();
    	};
    	
    private:
    	// The factory function just produce implementation.
    	template< typename TImplementation >
    	static std::shared_ptr<TInterface> ConstructImplementation( TArguments... arguments )
    	{
    		return std::static_pointer_cast<TInterface>(
    			std::make_shared<TImplementation>( std::forward<TArguments>( arguments )... )
    		);
    	};
    
    private:
    	// Factory function produces the implementations of TInterface.
    	using FactoryFunction	= std::shared_ptr<TInterface> (*)( TArguments... arguments );
    	
    	std::unordered_map<std::string, FactoryFunction>	m_factory_functions;
    };


    Работает она примерно так:
    cpp.sh/93obm
    Ответ написан
    Комментировать
  • Как коректно переобразовать тип wchar_t в string?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тип WCHAR зависит от настроек проекта и может быть как псевдонимом char, так и wchar_t. Широкое использование этого типа во всем проекте не рекомендуется, т.к. повсеместно вносит неопределенность.

    Для хранения Unicode строк (у тебя же 2017 студия и c++17 по умолчанию) уже давно существует сразу несколько типов строк: std::wstring, std::u16string и std::u32string.

    Тип std::wstring хранит символы типа wchar_t, который, в зависимости от настроек компилятора, может занимать 2 или 4 бйта. Это делает тип std::wstring столь же неоднозначным, как и WCHAR. Поэтому и были созданы типы со строгим размером символа: std::u16string и std::u32string. Сейчас рекомендуется пользоваться ими вместе с std::string.

    Сам твой вопрос заключается не в преобразовании типа, т.к. это легко сделать с помощью std::transform и лямбды, а в преобразовании однобайтовой (std::string) кодировки в Unicode и обратно.

    Для этого в стандартной библиотеке уже тоже есть всё нужное.
    Пример кода
    template< typename TCharType, typename TCharTraits, typename TStringAllocator >
    inline void Convert( const std::string& source_string, std::basic_string<TCharType, TCharTraits, TStringAllocator>& dest_string )
    {
    	std::wstring_convert<std::codecvt_utf8_utf16<TCharType>, TCharType> converter;
    	dest_string = converter.from_bytes( source_string );
    }
    
    template< typename TCharType, typename TCharTraits, typename TStringAllocator >
    inline void Convert( const std::basic_string<TCharType, TCharTraits, TStringAllocator>& source_string, std::string& dest_string )
    {
    	std::wstring_convert<std::codecvt_utf8_utf16<TCharType>, TCharType> converter;
    	dest_string = converter.to_bytes( source_string );
    }


    А помимо стандартной библиотеки все нужное есть еще и в WinAPI, и в C Run-Time Library.
    Ответ написан
    2 комментария
  • Каким образом operator>> попадает в глобальное прогстранство имён?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тебе стоит ознакомиться с понятием ADL.

    Because of argument-dependent lookup, non-member functions and non-member operators defined in the same namespace as a class are considered part of the public interface of that class (if they are found through ADL)

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

    Также через ADL доступны и дружественные функции, определенные по месту объявления дружественности и не имеющие предшествующего объявления.

    В итоге, operator>> никакими магическими силами не переносится в глобальное пространство имен. Он просто находится по ADL, т.к. правильно реализован и является частью интерфейса соответствующего типа.
    Ответ написан
    Комментировать
  • Что означает _t в типе wchar_t?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Это называется GNU/POSIX naming agreement - соглашение об именовании.
    The requirement that additional types defined in this section end in "_t" was prompted by the problem of name space pollution. It is difficult to define a type (where that type is not one defined by POSIX.1-2008) in one header file and use it in another without adding symbols to the name space of the program. To allow implementors to provide their own types, all conforming applications are required to avoid symbols ending in "_t", which permits the implementor to provide additional types. Because a major use of types is in the definition of structure members, which can (and in many cases must) be added to the structures defined in POSIX.1-2008, the need for additional types is compelling.


    Таким образом, для избежания пересечения имени типа с именами других сущностей, для имен типов определяется суффикс "_t".

    Дополнительно, GNU резервирует полностью все пространство имен для суффикса "_t" с целью расширения набора имен стандартных типов.

    В общем, суффикс "_t" несет двойной смысл. Первое - он говорит что это имя типа. Второе - он говорит что это имя стандартного типа.
    Ответ написан
    1 комментарий
  • Какую литературу по С++ и базовым знаниям выбрать для начинающего?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Путеводитель по граблям.
    Путеводитель по технологиям.
    Путеводитель по машинному обучению.

    Alex J. Champandard - AI Game Development (aigamedev.com; github; ai cources)
    Дополнительную литературу смотри на том-же амазоне по релевантности с книгой Алекса.

    Aurélien Géron - Hands-On Machine Learning
    Ответ написан
    Комментировать
  • Как раскрыть parameter pack для передачи типов в шаблонную функцию?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ошибка, которую видно в коде, это ошибка использования parameter pack.
    В коде у тебя видно раскрутку списка параметров, в ходе которой список неизбежно станет пустым и произойдет попытка инстанцировать вызов mapClasses<>(). И именно эта попытка приводит к ошибке.

    И эту ошибку можно убрать. Для этого у нас сейчас есть аж целых три способа.

    Способ первый - C++17 constexpr if.
    template< typename U, typename... args >
    void mapClasses()
    {
    	// ...
    	if constexpr( sizeof...( args ) > 0 ) // Все понятно и без слов.
    	{
    		mapClasses<args...>();
    	}
    }


    Способ второй - частная специализация класа/структуры.
    template< typename... args >
    struct Map;
    
    template< typename U >
    struct Map<U>
    {
    	static inline void mapClasses()
    	{
    		// ...
    	}
    };
    
    template< typename U, typename... args >
    struct Map<U, args...>
    {
    	static inline void mapClasses()
    	{
    		Map<U>::mapClasses();
    		Map<args...>::mapClasses();
    	}
    };


    Способ третий - самый коварррный - использование SFINAE в классическом его смысле.
    template< typename U >
    void mapClasses()
    {
    	// ...
    }
    
    // SFINAE тут (аргумент функции) выключит вывод шаблона при пустом списке полей.
    // В этом случае доступным остается только верхний экземпляр функции.
    template< typename U, typename... args >
    void mapClasses( char (*)[ sizeof...( args ) > 0 ] = 0 )
    {
    	mapClasses<U>();
    	mapClasses<args...>();
    }
    Ответ написан
    4 комментария
  • Как выделяется память в классах?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ответ на это есть в стандарте языка.
    Для начала, у нас есть спецификация размещения.
    Во вторых, у нас есть спецификация полей класса.
    10-й пункт спецификации полей класса говорит о том, что для полей класса недопустимы спецификаторы extern и thread_local, а значит допустимы только static и mutable.
    Спецификатор mutable не влияет на фактическое размещение поля класса.

    Отсюда выводится, что на размещение поля класса влияет только спецификатор static, который переносит поле в область постоянного размещения.

    В остальном, размещение всех нестатических полей класса производится внутри памяти создаваемого объекта.
    Ответ написан
    Комментировать
  • Разделение системы на модули?

    @MarkusD
    все время мелю чепуху :)
    Ты все правильно думаешь. Базовое разделение на модули выглядит рационально.
    А дальше давай думать логически.

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

    В итоге, нам надо завести утилитарную подсистему, связанную как с фильмами, так и с рейтингами.
    Для утилитарной подсистемы нормально знать сразу о многих других подсистемах, даже о других утилитарных. Поэтому в одной утилитарной подсистеме можно завести связи между фильмами и актерами, фильмами и внешними рейтингами, и.т.д.
    Чувство меры в этом деле работает на уровне богатства интерфейса по связи между подсистемами. Если связь между некоторым набором подсистем порождает богатый интерфейс, то этот интерфейс имеет смысл выдавить в отдельную подсистему.

    Более того, я обычно рекомендую полностью разделять данные и логику в разные подсистемы. Всякие там поиски, фильтрации и прочие любые манипуляции над неизменными данными лучше держать в отдельной подсистеме. Тогда всегда будет понятно, структура данных поменялась или метода манипуляции с этими данными.
    Ответ написан
    2 комментария
  • Как проверить на утечки памяти?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Больше трех лет пользуюсь инструментом Visual Leak Detector. Это тихий и неприхотливый инструмент, требующий соблюдения всего двух правил: вписать один include в одном cpp и собрать отладочную конфигурацию проекта.

    После завершения отлаживаемого процесса, VLD пишет в (файл/консоль/debug output) все найденные утечки со стеками выделения утекшей памяти. К отчету об утечке, помимо стека, еще добавляется HEX dump самого участка памяти, чтобы можно было на глаз сориентироваться, что за память утекла.
    А если утечек нет, VLD утешительно говорит что все нормально.

    Проект является открытым, доступен на github, стабильно развивается, выкладывается для современных версий Visual Studio и доступен из Extensions Manager самой студии.

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

    Ну и, безусловно, всегда есть valgrind memcheck, который по своей сути является sandbox-ом памяти и с радостью покажет тебе не только утечку, но и любой heap corruption, любое неправомерное обращение к памяти, которое ты будешь просто не в состоянии отловить руками или глазами.
    Насколько я помню, за последние 2-3 года valgrind так и не стал кроссплатформенным.
    Ответ написан
    4 комментария