Pull to refresh

Comments 15

Хотел бы уточнить две вещи:
1. Для чего создавать доп. файлы, но без расширения, например sstream и sstream.h?
2. Почему именно C++11, а не C++17, Там ведь добавили новые функции и т.д.?
1. Я хотел включить заголовочные файлы с теми же именами что и в стандартной библиотеке, но опять из-за багов компиляторов все пошло не так. Некоторые компиляторы считают что если в директиву #include включается файл без расширения, но при этом у него прописан путь, то значит ему нужно «дописать» .h в конце и искать его таким образом. Видимо по той логике, что файлы без расширения могут быть только от стандартной библиотеки, а значит путей не содержат.
Пример (файла type_traits нет в стандартной библиотеке, это заголовочный файл от моей библиотеки stdex):
stdex расположена в «C:\my_code\stdex\», путь до type_traits будет «C:\my_code\stdex\type_traits». Тогда в компиляторе:

include path = «C:\my_code\stdex\»
#include <type_traits> // OK

include path = «C:\my_code\»
#include <stdex/type_traits> // compilation error "can not include 'stdex/type_traits.h': no such file or directory"
#include <stdex/type_traits.hpp> // приходится использовать так

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

2. Потому что нужен был хотя бы C++ 11. И то не в полном объеме. Я реализовал что нужно было по минимуму, но посчитал что не дело оставлять реализацию на пол пути, да и увлекся я уже. Так что сейчас стараюсь нагнать хотя бы этот стандарт, а до последующих добраться уже имея реализацию C++ 11 будет проще намного.
Внимательный читатель уже говорил, что надо делать. В прошлый раз. :) Вот, собственно, как можно отличить enum от int'а:
#include <iostream>

struct true_type
{
	enum {value = 1};
};

struct false_type
{
	enum {value = 0};	
};

template<typename T>
struct is_integer_impl : false_type {};

#define DECLARE_INTEGER(T) \
template<> \
struct is_integer_impl<T> : true_type {}

DECLARE_INTEGER(char);
DECLARE_INTEGER(unsigned char);
DECLARE_INTEGER(signed char);
DECLARE_INTEGER(unsigned short);
DECLARE_INTEGER(signed short);
DECLARE_INTEGER(unsigned int);
DECLARE_INTEGER(signed int);
DECLARE_INTEGER(unsigned long);
DECLARE_INTEGER(signed long);

template<typename T>
struct is_integer : is_integer_impl<T> {};

template<typename T>
struct is_class
{
	typedef char yes;
	typedef int no;
	
	template<class U>
	static yes doCheck(void (U::*)());
	template<class U>
	static no doCheck(...);
	
	enum {value = (sizeof(doCheck<T>(0)) == sizeof(yes))};
};

template<typename T>
struct is_enum
{
	enum {value = !is_integer<T>::value && !is_class<T>::value};
};

enum TestEnum
{
	Item1,
	Item2
};

struct TestStruct
{
	
};

int main() 
{
	std::cout << is_integer<int>::value << std::endl;
	std::cout << is_enum<int>::value << std::endl;
	std::cout << is_class<int>::value << std::endl;
	std::cout << std::endl;
	std::cout << is_integer<TestStruct>::value << std::endl;
	std::cout << is_enum<TestStruct>::value << std::endl;
	std::cout << is_class<TestStruct>::value << std::endl;
	std::cout << std::endl;
	std::cout << is_integer<TestEnum>::value << std::endl;
	std::cout << is_enum<TestEnum>::value << std::endl;
	std::cout << is_class<TestEnum>::value << std::endl;

	return 0;
}

ideone.com/hqevaJ

В данном случае ручное (явное) перечисление целочисленных типов играет сильно на руку — подавляет компиляторную магию.
1. Внимательный читатель заметит что is_class определен через множество проверок не просто так (и в том числе зависит от is_enum если бы он был). =)
2.
is_class<EnumType>::value
будет true на некоторых компиляторах, т.к. по сути им без разницы что за контейнер. Если он не встроенный тип, то он по умолчанию считается имеющим member pointer.

Я думаю что можно как то хитро использовать свойство enum конвертироваться в int, или отсутствие конструторов/деструкторов/функций-членов класса. Но пока что к реализации этого не дошел, тестирую.
Ну, такой вариант is_class у нас успешно работает на довольно широкой линейке компиляторов — gcc начиная с 3.9, MSVC начиная с 2008, clang и некоторая экзотика. То есть я допускаю, что существуют компиляторы, которые поведут себя в этом случае иначе, но, к счастью, давно ими не приходилось пользоваться.
Борландовские версии к сожалению не работают, сегодня проверил :) Я долго пытался состряпать workaround, но похоже пациент совсем плохо умеет SFINAE — ничего не вышло.
Если автору важно поддерживать Borland C++, то вполне понятно почему он не применил это решение.
Нда. Как раз тот случай, когда жизнь — это боль… :( А ведь для своего времени Borland C++ был очень хорошим компилятором. Правда, не таким хорошим, как Watcom… :)
… проблема возникает с неполным типом T[] (массив без указания длины). Дело в том что данный тип не определяется некоторыми компиляторами (C++ Builder) при специализации шаблона, и универсальное решение здесь я пока что не нашел.

Проверил код ниже на C++ Builder 6 — работает.

namespace detail {

    template <typename T>
    static yes_type foo(T (*)[]);
    static no_type  foo(...);
    
    template <typename T>
    static T * declptr();
}

// is_array
template <class Tp>
struct is_array
{ 
    enum 
    { 
        value = sizeof(detail::foo(detail::declptr<Tp>())) == sizeof(yes_type) 
    }; 
};

template <class Tp, std::size_t Size>
struct is_array<Tp[Size]> 
    : true_type 
{ };
Это противоречит стандарту. Точнее теперь уже не противоречит с введением C++ 17, но до этого указатели на безразмерные массивы запрещены были. Они разрешены в чистом C, но в C++ их решили не переносить, т.к. использование их в кодовой базе на тот момент было очень ограничено, а поддержка для разработчиков компиляторов была слишком сложной.

На SO есть как раз вопрос с отличным ответом на этот счет.

Выдержка от туда:
Вопрос про функцию принимающую указатель на массив неизвестной длины и почему она не компилируется g++
int accumulate(int n, const int (*array)[])

Ответ
The committees decided that functions such as this, that accept a pointer or reference to an array with unknown bound, complicate declaration matching and overload resolution rules in C++. The committees agreed that, since such functions have little utility and are fairly uncommon, it would be simplest to just ban them. Hence, the C++ draft now states:
If the type of a parameter includes a type of the form pointer to array of unknown bound of T or reference to array of unknown bound of T, the program is ill-formed.
Таки шашечки или ехать? :)
В бусте тоже встречаются workaround`ы с нестандартными особенностями, обложенные проверками под конкретные компиляторы.
Ведь если мы работаем в условиях такой плохой поддержки стандарта, то сетовать на несоответствие стандарту некоторым образом лукавство, особенно, если код обеспечивает требуемое поведение.
Поэтому тут надо выбирать что важнее.

PS. Да, и я в курсе этих особенностей, т.к. сам некоторым образом решал подобную вашей задачу (пример: habr.com/post/277727), но для линейки старых GCC и Intel под Linux и BSD. Попробуйте вашу реализацию на GCC 2.x, возможно найдется множество интересных локальных задачек по нахождению обходных путей.
Довольно иронично то, что именно на этот вопрос я и отвечал в предыдущей статье про nullptr =)

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

P.S.: я именно вашу статью тоже с удовольствием давно уже прочел и плюс поставил. Это действительно очень занимательно, но у меня про другое немного.
Сама статья про другое, но написана она была по мотивам создания практически такого же набора инструментов, как у вас. Платформа только различается и целевые компиляторы.
Вы не подумайте, мне очень близка ваша идея строгого соответствия «букве» закона. Но я, как практик, при столкновении с подобными трудностями в своей реализации, делая выбор между «поддерживать фичу с оговорками» или «не поддерживать вовсе» выбираю первое :)

Взять, например, задачу определения наличия функции-члена класса с заданной сигнатурой, которая нерешаема в рамках стандартного С++98. Однако, эта возможность была необходима в инструментарии, который я создавал. Без нее было бы слишком неудобно им пользоваться. Поэтому я пошел на этот компромисс.

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

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

Кстати is_array как и is_enum я реализовал уже в полном объеме.
imho. А почему бы не назвать namespace stdx (по антологии xhtml (extertion html))? Так было бы, на мой взгляд, красивше, короче, да и почему и нет?

P.S. Чисто, ради интереса
Sign up to leave a comment.

Articles