2012-05-23 2 views
20

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

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

+1

Вы хотите определить, содержит ли класс тип ar? Или, если тип существует в области пространства имен? (Я не вижу, как последнее будет полезно). – sbabbi

+0

Компилятор сделает это за вас. Пожалуйста, объясните, что вы хотите сделать? –

+0

Чтобы уточнить - класс является просто жестко запрограммированным идентификатором, поэтому он не зависит от замены параметров шаблона вообще? – ndkrempel

ответ

25

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

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

Таким образом, проблема заключается в определении того, является ли T определенным типом класса.

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

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

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

std::is_constructible<T,int>::value 

будет справедливо, если T определено и ложно, если T просто декларируется.

Но это не переносится. <type_traits> в VC++ 2010 еще не обеспечивает std::is_constructible и даже его std::has_trivial_constructor<T> будет блевать, если 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) всякий раз, когда dt это выражение, которое вычисляется в вызове T::~T; и поэтому DT * будет также типом, который может использоваться в качестве аргумента функции перегрузки функции в принципе . Поэтому мы можем написать зонд, как это (GCC 4.6.3):

#ifndef HAS_DESTRUCTOR_H 
#define HAS_DESTRUCTOR_H 

#include <type_traits> 

/*! The template `has_destructor<T>` exports a 
    boolean constant `value that is true iff `T` has 
    a public destructor. 

    N.B. A compile error will occur if T has non-public destructor. 
*/ 
template< typename T> 
struct has_destructor 
{ 
    /* Has destructor :) */ 
    template <typename A> 
    static std::true_type test(decltype(std::declval<A>().~A()) *) { 
     return std::true_type(); 
    } 

    /* Has no destructor :(*/ 
    template<typename A> 
    static std::false_type test(...) { 
     return std::false_type(); 
    } 

    /* This will be either `std::true_type` or `std::false_type` */ 
    typedef decltype(test<T>(0)) type; 

    static const bool value = type::value; /* Which is it? */ 
}; 

#endif // EOF 

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

Смысл этого аргумента выражение std::declval<A>().~A() может быть неясным для некоторых, в частности, std::declval<A>().Шаблон функции std::declval<T>() определяется в <type_traits> и возвращает T&& (RValue ссылку на T) - хотя оно может быть использовано только в невычисленном контекстов, такие как аргумент decltype. Таким образом, значение std::declval<A>().~A() - a ~A() с некоторыми данными A. std::declval<A>() служит нам здесь, устраняя необходимость в том, чтобы там был любой общественный конструктор T, или для нас, чтобы узнать об этом.

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

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

#include "has_destructor.h" 
#include <iostream> 

class bar {}; // Defined 
template< 
    class CharT, 
    class Traits 
> class basic_iostream; //Defined 
template<typename T> 
struct vector; //Undefined 
class foo; // Undefined 

int main() 
{ 
    std::cout << has_destructor<bar>::value << std::endl; 
    std::cout << has_destructor<std::basic_iostream<char>>::value 
     << std::endl; 
    std::cout << has_destructor<foo>::value << std::endl; 
    std::cout << has_destructor<vector<int>>::value << std::endl; 
    std::cout << has_destructor<int>::value << std::endl; 
    std::count << std::has_trivial_destructor<int>::value << std::endl; 
    return 0; 
} 

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

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

#ifdef _MSC_VER 
namespace std { 
template <typename T> 
typename add_rvalue_reference<T>::type declval(); 
} 
#endif 
+0

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

+0

Привет, отличный ответ там, я пытаюсь изменить свой код для работы с шаблонами классов, пока я его скомпилирую, изменив «typename A/T» на «template class A/T ' чтобы соответствовать аргументам моего шаблона, но тест молча проваливается даже при наличии публичного деструктора. Вы знаете, почему это может быть так? Я знаю, что я мог бы создать шаблон для класса и использовать оригинал, но я использую эту проверку в макросе непосредственно перед тем, как специализироваться на шаблоне, поэтому я не могу создать экземпляр перед моей специализацией. Спасибо! – stellarpower

+0

Невозможно сказать, не видя свой код. Адаптация решения к шаблону класса работает прямо для меня, так что с вашим кодом что-то не так. Я предлагаю вам задать новый вопрос с подходящим заголовком и сказать: «Я пытаюсь безуспешно адаптировать <это решение> для работы с шаблоном класса. Вот мой код ...» –

0

Хорошо, я думаю, что нашел способ сделать это, хотя могут быть лучшие способы. Предположим, что мы имеем класс А, который включен в некоторые экземпляры библиотеки, а не в другие. Трюк состоит в том, чтобы определить специальный частный конструктор преобразования в 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>; 

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

+1

Это действительно не определяет, определен ли тип (или даже объявлен), а определяется ли макрос. В этом случае было бы проще просто оставить весь код и оставить его как '#if CLASS_DEFINED_MACRO', а затем зависимым кодом. –

+0

Я не следую. Дело в том, что невозможно проверить, объявлен ли тип, чтобы проверить, нужно ли его объявлять. Я думаю, что также невозможно определить, определен ли тип, если у вас нет крючка для поиска в типе. Макрос предоставляет этот крючок в виде конструктора преобразования. Мое решение позволяет вам скомпилировать библиотеку с определением класса и без него и проверить, определен ли класс с использованием SFINAE. Это так близко, что я думаю, мы можем добраться до того, чего хочет OP. Многие шаблоны требуют перехвата типов. Это все макрос здесь. –

+0

Кроме того, это не имеет ничего общего с SFINAE. –

5

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

namespace lib { 
#if DEFINE_A 
class A; 
#endif 
} 

namespace { 
    struct local_tag; 
    using A = local_tag; 
} 

namespace lib { 
    template <typename T = void> 
    A is_a_defined(); 
} 

constexpr bool A_is_defined = 
    !std::is_same<local_tag, decltype(lib::is_a_defined())>::value; 

Demo.

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

#if DEFINE_A 
class A; 
#endif 

namespace { 
    struct local_tag; 
    using A = local_tag; 
} 

namespace foo { 
    template <typename T = void> 
    ::A is_a_defined(); 
} 

constexpr bool A_is_defined = 
    !std::is_same<local_tag, decltype(foo::is_a_defined())>::value; 

Demo.

2

Еще не нашли удовлетворительный ответ в этом посте ...

Майк Kinghan начал правильный ответ и сказал умную вещь:

Таким образом, проблема заключается в определении Т, является ли определенный тип класса.

Но

SizeOf (T) не поможет здесь

не правильно ...

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

template <class T, class Enable = void> 
struct is_defined 
{ 
    static constexpr bool value = false; 
}; 

template <class T> 
struct is_defined<T, std::enable_if_t<(sizeof(T) > 0)>> 
{ 
    static constexpr bool value = true; 
}; 
+0

Этот ответ прост для понимания и работы для меня на обоих GCC 7.2.1 и VS 2017. Принимая во внимание, что самый высокий ответ дает мне ошибки «неполного типа» на GCC. Престижность для этого :) – Kai

Смежные вопросы