Вопрос по typetraits, templates, c++ – Как написать шаблон `is_complete`?

18

После ответаэто вопрос, который я пытался найти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, но там есть платформа для конкретногорешение который работает для меня.

В C ++ 03 также нет способа сделать это. По крайней мере, с C ++ 0x с "sfinae для выражений", но даже тогда, если вы передадитеvector<int> например иvector только объявлен, но не определен, тогда проверка на полноту приведет к неявной реализации, и если определение недоступно, выдаст серьезную ошибку, которая не покрыта sfinae (ошибка не находится в «непосредственном контексте»). Johannes Schaub - litb
Компилятор знает, что тип здесь неполный, а затем завершает его, возможно, несколькими строками позже; скажем после новой декларации или специализации. Конечно, эта информация, в принципе, может быть доступна. user2023370
Большой вопрос почему. Не задумываясь, зачем вам это нужно, как знает компилятор во время компиляции. Martin York
Я думаю, что это не может работать в принципе (кроме случаев, когда вы всегда применяете его к типу, который всегда остается неполным или всегда полным). Потому что лиU завершено или нет,is_complete<U> всегда указывает один и тот же тип. Если вы сейчас идете и используетеis_complete<U> в двух разных единицах трансляции значение элемента, возможно, будет иметь разное значение каждый раз, и компилятор свободен в том, какое значение он использует. Я думаю, это неверно, но я не могу найти заявление Стандарта по этому поводу :( Был бы рад, если бы вы смогли это выяснить. Johannes Schaub - litb
@litb: я бы сказал, что это обрабатывается комбинацией 14.4 / 1 (эквивалентность типа для идентификатора шаблона) и затем 3.2 / 5 bullet 2, которая требует, чтобы имена ссылались на одну и ту же сущность. Если один и тот же идентификатор шаблона приводит к имени, относящемуся к разным объектам, это является нарушением ODR. Richard Corden

Ваш Ответ

7   ответов
1

чтобы показать, что ответ (не даный мной) на несвязанный вопрос дает решение дляis_complete<T> шаблон.

ОтветВот, Я не вставляю это ниже, чтобы не по ошибке получить кредит на это.

2

необходимо выполнить вычисления в аргументе шаблона признака по умолчанию, так как попытка изменить определение шаблона нарушает правило 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.

Правило приведено в §8.3.6 / 9, которое в равной степени применяется к аргументам по умолчанию для функции и аргументам шаблона по умолчанию:

Аргументы по умолчанию оцениваются каждый раз, когда вызывается функция.

Но будьте осторожны, использование этого внутри шаблона почти наверняка нарушит ODR. Шаблон, созданный для неполного типа, не должен делать что-либо иначе, чем если бы он был создан для полного типа. Я лично хочу это только дляstatic_assert.

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

9

но до сих пор ни одно решение 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 >
{};

Этот вдохновлен из интернета истатические утверждают, что шаблон typename T НЕ завершен?

@ Роджер, это хорошие новости! Спасибо друг. mister why
Идеально для меня работал на всех протестированных мной компиляторах и придерживается более привычного стиля для свойств пользовательских типов, не полагаясь на специфику реализации__COUNTER__ стоимость. Я считаю, что это лучшее решение, чем принятый ответ. Roger Sanders
13

данный Алексеем Малистовым, можно использовать на 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

К сожалению,СЧЕТЧИК предопределенный макрос не является частью стандарта, поэтому он не будет работать на каждом компиляторе.

Это нарушает правило ODR. Счетчик получает несколько разных специализаций в одной единице перевода, но если вы используете его во втором файле, та же специализация получит несовместимое определение. Решение состоит в том, чтобы поместить его в безымянное пространство имен. Potatoswatter
__COUNTER__ поддерживается MSVC и с gcc начиная с 4.3. Тем не менее, с GCC, проблема не в том, чтобы подделать разныеis_complete типы с помощью__COUNTER__ но с компилятором сделать SFINAE и выбратьpass(...) перегрузки. Gregory Pakosz
Это не совместимо с полными абстрактными типами. На MSVC 2008IS_COMPLETE(MyAbstractClass) не компилируется. И даже если компилятор разрешил это и смог сделать SFINAE с абстрактным типом, кажется, что он дал бы неправильный ответ (то есть он сообщил бы, что полный абстрактный тип на самом деле был неполным). bshields
@Potatoswatter Вы правы. Я отредактировал ответ. J. Calleja
2

is_complete Тип черт. Реализация, заданная @Alexey, не компилируется на G ++ 4.4.2 и G ++ 4.5.0:

ошибка: инициализация аргумента 1 статического символа (& is_complete :: pass (T)) [2] [с T = Foo] ’

На моем Mac, с оценкой G ++ 4.0.1is_complete<Foo>::value гдеstruct Foo; неполно уступаетtrue что даже хуже, чем ошибка компилятора.

T может быть как полным, так и неполным в одной и той же программе, в зависимости от единицы перевода, но это всегда один и тот же тип. Как следствие, как отмечено выше,is_complete<T> всегда один и тот же тип.

Так что, если вы уважаетеODR невозможно иметьis_complete<T> оценивая различные значения в зависимости от того, где он используется; в противном случае это будет означать, что у вас есть разные определенияis_complete<T> который ODR запрещает.

РЕДАКТИРОВАТЬ: Как принятый ответ, я сам взломал решение, которое использует__COUNTER__ макрос для создания другого экземпляраis_complete<T, int> введите каждый разIS_COMPLETE макрос используется. Однако с gcc я не мог заставить SFINAE работать в первую очередь.

0

что гарантировало бы, что sizeof для неполного типа приведет к 0. Однако требуется, чтобы, если T в какой-то момент был неполным, но завершенным позже в этой единице перевода, все ссылки на T ссылались к тому же типу - так, как я читаю, даже если T является неполным там, где был вызван ваш шаблон, необходимо будет сказать, что оно было завершено, если T завершено где-то в этом блоке перевода.

"что если T в какой-то момент является неполным, но завершено позже в этой единице перевода, то все ссылки на T относятся к одному и тому же типу"действительно, пользовательский тип ненеотъемлемо полный или неполный, это только неполное, пока не будет завершено (void по своей сути неполный). Так что вся идея чрезвычайно проблематична. curiousguy
sizeof плохо сформирован для неполных типов и не может дать ноль, так как C ++ 98:sizeof оператор не должен применяться к выражению, которое имеет ... неполный тип, ... или к имени таких типов в скобках. "Обработка GCC не соответствует, но в любом случае рекомендуется поддерживать его, если пишется такая возможность. Potatoswatter
9
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;
};
компиляция с помощью gcc 4.4.2 Gregory Pakosz
то же самое с gcc 4.5.0 Gregory Pakosz
завершается с ошибкой: инициализируется аргумент 1 статического символа (& is_complete <T> :: pass (T)) [2] [с T = Foo] ’ Gregory Pakosz
Хорошо, но, как говорит @litb в своем комментарии, он не будет работать должным образом, если is_complete <type> появляется в двух противоречащих друг другу местах в одном и том же файле, когда между ними появляется определение типа класса (я пытался :)). Asaf

Похожие вопросы