Как определить существование classа с использованием SFINAE?

Можно ли определить, существует ли class на C ++ с использованием SFINAE ? Если возможно, то как?

Предположим, что у нас есть class, который предоставляется только некоторыми версиями библиотеки. Я хотел бы знать, можно ли использовать SFINAE для определения того, существует ли class или нет. Результат обнаружения произволен, скажем, константа enums, которая равна 1, если она существует, 0 в противном случае.

Если мы попросим компилятор рассказать нам что-либо о типе classа T , который даже не был объявлен, мы обязаны получить ошибку компиляции. Это не так. Поэтому, если мы хотим знать, существует ли class T “, где T еще не объявлен, мы должны сначала объявить T

Но это нормально, потому что просто объявление T не сделает его «существующим», поскольку то, что мы должны подразумевать под T существует, определяется T И если, объявив T , вы можете определить, уже ли он определен , вам не нужно путаться.

Поэтому задача состоит в том, чтобы определить, является ли T определенным типом classа.

sizeof(T) здесь не помогает. Если T не определено, это даст incomplete type T ошибку incomplete type T Аналогично typeid(T) . Также нет хорошего создания зонда SFINAE для типа T * , поскольку T * является определенным типом, если T объявлен, даже если T нет. И поскольку мы обязаны иметь объявление classа T , std::is_class тоже не является ответом, потому что для этого объявления достаточно сказать «Да».

C ++ 11 предоставляет std::is_constructible в . Может ли это предложить решение без привязки? – учитывая, что если T определено, то он должен иметь хотя бы один конструктор.

Я не боюсь. Если вы знаете подпись хотя бы одного публичного конструктора T то GCC ( с 4.6.3) действительно сделает бизнес. Скажем, что один известный публичный конструктор T::T(int) . Затем:

 std::is_constructible::value 

будет истинным, если T определено и false, если T просто объявлено.

Но это не переносимо. в VC ++ 2010 еще не предоставляет std::is_constructible и даже его std::has_trivial_constructor будет barf, если T не определено: скорее всего, когда std::is_constructible придет, он будет следовать его примеру. Кроме того, в случае, когда существуют только частные конструкторы T для предложения std::is_constructible тогда даже GCC будет barf (что является брови для поднятия бровей).

Если T определен, он должен иметь деструктор и только один деструктор. И этот деструктор является более вероятным публичным, чем любой другой возможный член T В этом свете самой простой и сильной игрой, которую мы можем сделать, является создание зонда SFINAE для существования T::~T

Этот пробник SFINAE не может быть создан обычным способом для определения того, имеет ли T обычную функцию-член mf – что делает «Да» перегрузкой функции SFINAE-зонда принимать аргумент, который определяется в терминах типа &T::mf . Потому что нам не разрешено принимать адрес деструктора (или конструктора).

Тем не менее, если T определено, то T::~T имеет тип DT – который должен быть decltype(dt) методом decltype(dt) когда dt является выражением, которое вычисляется при вызове T::~T ; и поэтому DT * будет типом, который в принципе может быть задан как тип аргумента перегрузки функции. Поэтому мы можем написать такой зонд (GCC 4.6.3):

 #ifndef HAS_DESTRUCTOR_H #define HAS_DESTRUCTOR_H #include  /*! The template `has_destructor` exports a boolean constant `value that is true iff `T` has a public destructor. NB A compile error will occur if T has non-public destructor. */ template< typename T> struct has_destructor { /* Has destructor :) */ template  static std::true_type test(decltype(std::declval().~A()) *) { return std::true_type(); } /* Has no destructor :( */ template static std::false_type test(...) { return std::false_type(); } /* This will be either `std::true_type` or `std::false_type` */ typedef decltype(test(0)) type; static const bool value = type::value; /* Which is it? */ }; #endif // EOF 

с единственным ограничением, что T должен иметь публичный деструктор, который должен быть юридически выведен в выражении аргумента decltype(std::declval().~A()) . ( has_destructor представляет собой упрощенную адаптацию шаблона для интроспекции метода, который я внесла здесь .)

Значение этого аргумента выражение std::declval().~A() может быть неясным для некоторых, в частности std::declval() . Шаблон функции std::declval() определяется в и возвращает T&& (rvalue-reference to T ) – хотя он может быть вызван только в неоцененных контекстах, таких как аргумент decltype . Таким образом, значение std::declval().~A() является вызовом ~A() для некоторого заданного A std::declval() хорошо std::declval() нам, устраняя необходимость существования какого-либо публичного конструктора T или нам об этом знать.

Соответственно, тип аргумента зонда SFINAE для «Yes overload»: указатель на тип деструктора A , а test(0) будет соответствовать этой перегрузке только в том случае, если существует такой тип, как деструктор A , для A = T

С has_destructor в руке – и его ограничение на публично разрушаемые значения T в уме – вы можете проверить, определен ли class T в какой-то момент вашего кода, гарантируя, что вы объявите его, прежде чем задавать вопрос. Вот тестовая программа.

 #include "has_destructor.h" #include  class bar {}; // Defined template< class CharT, class Traits > class basic_iostream; //Defined template struct vector; //Undefined class foo; // Undefined int main() { std::cout << has_destructor::value << std::endl; std::cout << has_destructor>::value << std::endl; std::cout << has_destructor::value << std::endl; std::cout << has_destructor>::value << std::endl; std::cout << has_destructor::value << std::endl; std::count << std::has_trivial_destructor::value << std::endl; return 0; } 

Построенный с GCC 4.6.3, это скажет вам, что classы 2 // Defined имеют деструкторы, а classы 2 // Undefined - нет. Пятая строка выводит, что int разрушает, а заключительная строка покажет, что std::has_trivial_destructor согласен. Если мы хотим сузить поле до типов classов, std::is_class может быть применен после того, как мы определим, что T является разрушаемым.

Visual C ++ 2010 не предоставляет std::declval() . Чтобы поддержать этот компилятор, вы можете добавить следующее вверху has_destructor.h :

 #ifdef _MSC_VER namespace std { template  typename add_rvalue_reference::type declval(); } #endif 

С SFINAE нет. Я думаю, что приемы поиска имени – это способ сделать это. Если вы не боитесь вводить имя в пространство имен библиотеки:

 namespace lib { #if DEFINE_A class A; #endif } namespace { struct local_tag; using A = local_tag; } namespace lib { template  A is_a_defined(); } constexpr bool A_is_defined = !std::is_same::value; 

Demo.

Если A объявлено в глобальном пространстве имен:

 #if DEFINE_A class A; #endif namespace { struct local_tag; using A = local_tag; } namespace foo { template  ::A is_a_defined(); } constexpr bool A_is_defined = !std::is_same::value; 

Demo.

Все еще не нашел удовлетворительного ответа в этом посте …

Майк Кингхэн ответил правильно и сказал умное:

Поэтому задача состоит в том, чтобы определить, является ли T определенным типом classа.

Но

sizeof (T) здесь не помогает

не является правильным…

Вот как вы можете это сделать с sizeof(T) :

 template  struct is_defined { static constexpr bool value = false; }; template  struct is_defined 0)>> { static constexpr bool value = true; }; 

Хорошо, я думаю, что нашел способ сделать это, хотя могут быть и лучшие способы. Предположим, что мы имеем class А, который включен в некоторые экземпляры библиотеки, а не в другие. Трюк состоит в том, чтобы определить специальный частный конструктор преобразования в A, а затем использовать SFINAE для обнаружения конструктора преобразования. Когда A включен, обнаружение завершается успешно; когда это не так, обнаружение не срабатывает.

Вот конкретный пример. Сначала заголовок для шаблона обнаружения, class_defined.hpp:

 struct class_defined_helper { }; template< typename T > struct class_defined { typedef char yes; typedef long no; static yes test( T const & ); static no test( ... ); enum { value = sizeof( test( class_defined_helper( )) == sizeof( yes ) }; }; #define CLASS_DEFINED_CHECK( type ) \ type( class_defined_helper const & ); \ \ friend struct class_defined< type >; 

Теперь заголовок, содержащий определение classа, blah.hpp:

 #include "class_defined.hpp" #ifdef INCLUDE_BLAH class blah { CLASS_DEFINED_CHECK( blah ); }; #else class blah; #endif 

Теперь исходный файл main.cpp:

 #include "blah.hpp" int main( ) { std::cout << class_defined< blah >::value << std::endl; } 

Скомпилированный с BLAH_INCLUDED определил эти отпечатки 1. Без BLAH_INCLUDED он определил, что он печатает 0. К сожалению, это все равно требует, чтобы в обоих случаях компилировалось форвардное объявление classа. Я не вижу способа избежать этого.

Interesting Posts
Давайте будем гением компьютера.