Как написать шаблон `is_complete`?
-
06-07-2019 - |
Вопрос
После ответа на этот вопрос, который я пытался найти is_complete
, шаблон в Boost библиотека, и я понял, что в Boost.TypeTraits такого шаблона нет. Почему такого шаблона нет в библиотеке Boost? Как это должно выглядеть?
//! Check whether type complete
template<typename T>
struct is_complete
{
static const bool value = ( sizeof(T) > 0 );
};
...
// so I could use it in such a way
BOOST_STATIC_ASSERT( boost::is_complete<T>::value );
Приведенный выше код неверен, поскольку применять sizeof
к неполному типу недопустимо. Каким будет хорошее решение? Можно ли как-то применить SFINAE в этом случае?
Ну, в общем, эту проблему невозможно решить без нарушения правила ODR , но есть платформенное решение , которое работает для меня . р>
Решение
Ответ, данный Алексеем Малистовым, можно использовать в MSVC с незначительной модификацией:
namespace
{
template<class T, int discriminator>
struct is_complete {
static T & getT();
static char (& pass(T))[2];
static char pass(...);
static const bool value = sizeof(pass(getT()))==2;
};
}
#define IS_COMPLETE(X) is_complete<X,__COUNTER__>::value
К сожалению, предопределенный макрос COUNTER не является частью стандарта, поэтому он не будет работать на каждом компиляторе.
Другие советы
template<class T>
struct is_complete {
static T & getT();
static char (& pass(T))[2];
static char pass(...);
static const bool value = sizeof(pass(getT()))==2;
};
Это может быть немного поздно, но до сих пор ни одно решение C ++ 11 не работало как для полных, так и для абстрактных типов.
Итак, вот и вы.
В VS2015 (v140) g ++ > = 4.8.1, clang > = 3.4, это работает:
template <class T, class = void>
struct IsComplete : std::false_type
{};
template <class T>
struct IsComplete< T, decltype(void(sizeof(T))) > : std::true_type
{};
Благодаря Бат-Ульзии Лувсанбату: https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update -1 / р>
С VS2013 (V120):
namespace Details
{
template <class T>
struct IsComplete
{
typedef char no;
struct yes { char dummy[2]; };
template <class U, class = decltype(sizeof(std::declval< U >())) >
static yes check(U*);
template <class U>
static no check(...);
static const bool value = sizeof(check< T >(nullptr)) == sizeof(yes);
};
} // namespace Details
template <class T>
struct IsComplete : std::integral_constant< bool, Details::IsComplete< T >::value >
{};
Это вдохновлено интернет-сетями и статическим утверждением, что шаблонное имя T не является полным?
Боюсь, вы не можете реализовать такие черты типа is_complete
. Реализация, заданная @Alexey, не компилируется в G ++ 4.4.2 и G ++ 4.5.0:
ошибка: инициализация аргумента 1 из & # 8216; статический символ (& amp; is_complete :: pass (T)) [2] [with T = Foo] & # 8217;
На моем Mac с оценкой G ++ 4.0.1 is_complete<Foo>::value
где struct Foo;
является неполным, получается true
, что даже хуже, чем ошибка компилятора.
T
может быть как полным, так и неполным в одной и той же программе, в зависимости от единицы перевода, но это всегда один и тот же тип. Как следствие, как указано выше, is_complete<T>
также всегда одинакового типа.
Поэтому, если вы уважаете ODR , невозможно __COUNTER__
оценивать разные значения в зависимости от того, где он используется; в противном случае это будет означать, что у вас есть различные определения для is_complete<T, int>
, которые запрещены ODR.
РЕДАКТИРОВАТЬ: Как принятый ответ, я сам взломал решение, использующее макрос IS_COMPLETE
для создания экземпляра другого типа <=> каждый раз, когда используется макрос <=>. Однако с gcc я не смог заставить SFINAE работать.
Чтобы решить эту проблему, необходимо выполнить вычисления в аргументе шаблона признака по умолчанию, поскольку попытка изменить определение шаблона нарушает правило ODR (хотя комбинация __COUNTER__
и namespace {}
может обойти ODR). р>
Это написано на C ++ 11, но может быть настроено для работы в режиме C ++ 03 умеренно недавнего C ++ 11-совместимого компилятора.
template< typename t >
typename std::enable_if< sizeof (t), std::true_type >::type
is_complete_fn( t * );
std::false_type is_complete_fn( ... );
template< typename t, bool value = decltype( is_complete_fn( (t *) nullptr ) )::value >
struct is_complete : std::integral_constant< bool, value > {};
Аргумент по умолчанию оценивается там, где указан шаблон, поэтому он может контекстно переключаться между различными определениями. Нет необходимости в разной специализации и определении при каждом использовании; вам нужен только один для true
и один для false
.
Правило приведено в & # 167; 8.3.6 / 9, которое в равной степени применяется к аргументам по умолчанию для функции и аргументам шаблона по умолчанию:
Аргументы по умолчанию оцениваются каждый раз, когда вызывается функция.
Но будьте осторожны, использование этого внутри шаблона почти наверняка нарушит ODR. Шаблон, созданный для неполного типа, не должен делать что-либо иначе, чем если бы он был создан для полного типа. Лично я хочу это только для static_assert
.
Кстати, этот принцип также может быть полезен, если вы хотите пойти другим путем и реализовать функциональность <=> с использованием шаблонов и перегрузок.
Просто позвоните, чтобы показать, что ответ (не даный мной) на несвязанный вопрос дает решение для шаблона is_complete<T>
.
Ответ здесь . Я не буду вставлять его ниже, чтобы по ошибке не получить за это кредит.
Я не могу найти в стандарте ничего, что гарантировало бы, что sizeof для неполного типа даст 0. Это требует, однако, что, если T в какой-то момент является неполным, но завершено позже в этом блоке перевода, все ссылки к T относятся к тому же типу - так как я читаю его, даже если T является неполным там, где был вызван ваш шаблон, необходимо будет сказать, что оно было завершено, если T завершено где-то в этом модуле перевода.