Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

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

С++ 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 определено и false, если T просто объявлено.

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

Если T определен, он должен иметь деструктор и только один деструктор. И это destructor является более доступным, чем любой другой возможный член 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> - упрощенная адаптация метода-интроспекции template Я внесла здесь.)

Значение выражения этого аргумента std::declval<A>().~A() может быть неясный для некоторых, в частности std::declval<A>(). Шаблон функции std::declval<T>() определяется в <type_traits> и возвращает T&& (rvalue-reference to T) - хотя он может быть вызван только в неоцененных контексты, такие как аргумент decltype. Таким образом, значение std::declval<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 С++ 2010 не предоставляет std::declval(). Чтобы поддержать этот компилятор вы можете добавить следующее вверху has_destructor.h:

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

Ответ 2

С 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;

Демо-версия

Если 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;

Демо-версия

Ответ 3

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

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

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

Но

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;
};

Ответ 4

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