Ответы пользователя по тегу C++
  • Какие есть C++ библиотеки для логирования в формате JSON?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Вот интересная статья в качестве источника информации.
    https://habr.com/ru/post/313686/

    Как правило у библиотеки логирования есть кастомизация принтеров, позволяющая хоть в бинарном формате логи вести.
    Ответ написан
  • Можно ли передать в функцию-предикат значение как в лямбду?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Лямбда - это объект неименованного типа, не являющегося типом объединеня или агрегатным типом.
    Одним из свойств лямбды является то, что для случая с пустым замыканием лямбда неявно приводится к указателю на глобальную функцию. В ином случае лямбда - это объект-функтор, у которого определен operator().

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

    Контекст можно расположить, скажем, в глобальном пространстве имен в виде глобальных переменных.
    int N = 0;
    bool GreatherThanN(int x) {
        if (x > N) {
            return true;
        }
        return false;
    }

    Такой подход, мягко говоря, неидеален. Его просто стоит знать, как врага, в лицо. :)

    Другим способом является определение контекста через параметры шаблона функции:
    template< int N >
    bool GreatherThanN(int x) {
        if (x > N) {
            return true;
        }
        return false;
    }

    Инстанцирование функции из такого шаблона пройдет через оптимизации и с высокой вероятностью выродится в константу в месте вызова. Но N в этом случае надо знать на этапе компиляции. Да и в целом, не всё можно определить через параметры шаблона. Параметром шаблона может быть или тип, или константа целочисленного типа или типа сводимого к целочисленному (enum, bool, const char*, известные на этапе компиляции указатели).

    Третьим случаем определения контекста является использование т.н. функторов - функциональных объектов.
    Контекст в этом случае укладывается в объекте, а сама функция заключается в operator().
    struct Comparator final
    {
    	int N;
    	
    	inline const bool operator () ( int x ) const
    	{
    		return x > N;
    	}
    };
    
    // ...
    
    Comparator GreatherThanN{ 10 };
    
    // ...
    
    count_if(begin(v), end(v), GreatherThanN);

    Но с появлением лямбд в C++11 такой подход резко убавил в популярности, т.к. его задачи теперь с большим успехом решают лямбды.
    Ответ написан
    Комментировать
  • Что делает оператор ":" в этом случае в c++?

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

    Еще начиная с C++11 в секции инициализации можно указывать делегирование конструкторов. Выбранный конструктор будет так же выполнен до входа в тело текущего конструктора.
    Ответ написан
    1 комментарий
  • Как инициализировать изменяемый 2D массив char в struct C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Вот так сработает.
    Пример
    #include <iostream>
    using namespace std;
    
    const char ssid[] = "TEST";
    const char password[] = "123456";
    
    struct Wifi {
        const char *access[2][2];
    } wifi{
        {
            { ssid, password },
            { "DIR", "654321" }
        }
    };
    
    int main() {
    
        for (int i = 0; i < sizeof(wifi.access) / sizeof(wifi.access[0]); i++) {
            cout << wifi.access[i][0] << ": " << wifi.access[i][1] << '\n';
            // TEST: 123456
            // DIR: 654321
        }
    
        return 0;
    }

    Смотри на агрегатную инициализацию.
    Ответ написан
    2 комментария
  • Зачем нужно выравнивание памяти по слову?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Разобраться с вопросом тебе поможет статья: Расставим точки над структурами C/C++.
    Если точнее, то тебе стоит детально изучить первый же пункт статьи: Выравнивание полей памяти.
    В общем выравниваются в памяти поля по границе кратной своему же размеру. То есть 1-байтовые поля не выравниваются, 2-байтовые — выравниваются на чётные позиции, 4-байтовые — на позиции кратные четырём и т.д.
    Ответ написан
    1 комментарий
  • 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 комментарий