Вопрос по c++ – Как обнаружить существование класса с помощью SFINAE?

22

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

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

Для пояснения - класс является просто жестко идентифицированным идентификатором, так что он вообще не зависит от подстановки параметров шаблона? ndkrempel
@dirkgently: Да, в идеальном мире это можно сделать, проверив макросы версий или что-то в этом роде. К сожалению, разработчики библиотек не всегда предоставляют такую информацию. vitaut
Вы хотите определить, содержит ли класс определенный тип? Или, если тип существует в области имен? (Я не вижу, как последний будет полезен) sbabbi
@sbabbi: класс в области имен. Это может быть очень полезно в качестве замены / дополнения к этапу сборки, подобному настройке. vitaut
Компилятор сделает это за вас. Пожалуйста, объясните, что вы хотите сделать? Jesse Good

Ваш Ответ

4   ответа
30

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

Но это нормально, потому что просто объявивT не сделает его "существующим", поскольку что мы должны подразумевать подT exists являетсяT is defined, И если, заявивT, you can then determine whether it is already definedвам не нужно быть в любая путаница

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

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

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

Я не боюсь. Если вы знаете подпись хотя бы одного публичного конструкторT затем GCC<type_traits> (по состоянию на 4.6.3) действительно будет бизнес. Скажите, что один известный публичный конструктор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 определяется, он должен иметьdestructorи только один деструктор. И это деструктор, скорее всего, будет публичным, чем любой другой возможный членT, В этот свет, самая простая и сильная игра, которую мы можем сделать, это создать SFINAE зонд на наличиеT::~T.

Этот зонд SFINAE не может быть обработан обычным способом для определения будь тоT имеет обычную функцию-членmf - создание «Да перегрузки»; функции зонда SFINAE принимает аргумент, который определяется в терминах изthe type of &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 должен иметьpublic разрушитель быть юридически вызывается в выражении аргументаdecltype(std::declval<A>().~A()). (has_destructor<T> упрощенная адаптация метода-интроспекции шаблон, который я внесВот.)

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

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

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

#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
Это действительно хороший анализ. Я думаю, что это фактически эквивалентно предложенному мною решению, за исключением того, что он использует деструктор в качестве ловушки вместо того, чтобы требовать специального конструктора преобразования. Здесь есть компромисс - это решение будет работать прозрачно большую часть времени, но мое будет работать все время за счет вызова макроса в каждом определении класса.
Привет, отличный ответ. Я пытаюсь изменить ваш код для работы с шаблонами классов, поэтому я могу его скомпилировать, изменив "typename A / T"; to 'template & lt; unsigned char, class & gt; класс A / T ' чтобы соответствовать аргументам моего шаблона, но тест молча проваливается, даже если есть публичный деструктор. Вы знаете, почему это может быть, пожалуйста? Я знаю, что могу создать экземпляр шаблона для класса и использовать оригинал, но я использую эту проверку в макросе непосредственно перед специализацией шаблона, поэтому я не могу создать его перед своей специализацией. Спасибо!
Невозможно сказать, не видя ваш код. Адаптация решения к шаблону класса работает для меня прямо, поэтому с вашим кодом что-то не так. Я предлагаю вам задать новый вопрос с подходящим названием и сказать: «Я пытаюсь безуспешно адаптировать & lt; это решение & gt; работать для шаблона класса. Вот мой код ... & quot;
0

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

За исключением того, что это не имеет никакого отношения к СФИНАЕ.
Я не следую. Дело в том, что невозможно проверить, объявлен ли тип, поскольку для того, чтобы проверить, нужно ли его объявлять. Я думаю, что также невозможно определить, определен ли тип, если у вас нет хука для поиска в типе. Макрос предоставляет этот хук в форме конструктора преобразования. Мое решение позволяет вам скомпилировать библиотеку с определением класса и без него и проверить, определен ли класс с помощью SFINAE. Это настолько близко, насколько я думаю, мы можем достичь того, что хочет ОП. Многие шаблоны требуют крючков в типах. Вот и весь макрос, который мы здесь используем.
Это на самом деле не определяет, определен ли тип (или даже объявлен), а определен ли макрос. В этом случае может быть проще просто отбросить весь код и оставить его как#if CLASS_DEFINED_MACRO а затем зависимый код.
6

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

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.

7

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

So the problem is to determine whether T is a defined class type.

Но

sizeof(T) is no help here

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

Вот как вы можете сделать это с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;
};
@IsaacPascual, да, и именно поэтому он работает: вторая замена завершается неудачно, когда тип является неполным. На самом деле, это может быть переименовано вis_complete но в классе это простоis_defined, Кстати, через год я только что понял, чтоsizeof(T) > 0 можно заменить простоsizeof(T).
Этот ответ прост для понимания и работает для меня как в GCC 7.2.1, так и в VS 2017. Принимая во внимание, что ответ с самым высоким рейтингом дает мне «неполный тип» ошибки в GCC. Слава за это :)
От standart & quot; Оператор sizeof не должен применяться к выражению, имеющему функцию или неполный тип, к типу перечисления, базовый тип которого не фиксирован до объявления всех его перечислителей, к названию таких типов в скобках или glvalue, который обозначает битовое поле. & quot;

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