32

SFINAEを使用してクラスがC++に存在するかどうかを検出することは可能ですか?可能であれば、どのように?

ライブラリの一部のバージョンによってのみ提供されるクラスがあるとします。SFINAEを使用してクラスが存在するかどうかを検出できるかどうかを知りたいのですが。検出の結果は任意です。たとえば、列挙型定数が存在する場合は1、存在しない場合は0です。

4

4 に答える 4

40

T宣言されていないクラス型についてコンパイラーに何かを教えてもらうと、コンパイルエラーが発生します。それを回避する方法はありません。 したがって、まだ宣言されていない可能性があるクラスTが「存在する」かどうかを知りたい場合は、最初に宣言する必要があります。TT

しかし、それは問題ありません。なぜなら、単に宣言Tするだけでは「存在する」ことにはならないからです。なぜなら、T存在することによって意味しなければならないことはT定義されているからです。そして、宣言したT後、それがすでに定義されているかどうかを判断できる場合は、混乱する必要はありません。

したがって、問題はT、が定義されたクラスタイプであるかどうかを判断することです。

sizeof(T)ここでは役に立ちません。Tが未定義の 場合、incomplete type Tエラーが発生します。同様にtypeid(T)。また、宣言されていない場合でも、宣言されている限り、定義されたタイプであるT *ため、タイプでSFINAEプローブを作成することは適切ではありません。そして、クラスの宣言が義務付けられているので、それも答えではありません。なぜなら、その宣言は「はい」と言うのに十分だからです。T * TTTstd::is_class<T>

C++11はで提供std::is_constructible<T ...Args><type_traits>ます。これはオフザペグソリューションを提供できますか?-Tが定義されている場合、少なくとも1つのコンストラクターが必要です。

そうではないと思います。少なくとも1つのパブリックコンストラクターの署名を知っている場合はT、GCC <type_traits>(4.6.3現在)が実際にビジネスを行います。既知のパブリックコンストラクターの1つがであるとしT::T(int)ます。それで:

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

Tが定義されている場合はtrueになり、T単に宣言されている場合はfalseになります。

しかし、これは移植性がありません。<type_traits>VC ++ 2010では、まだ提供されstd::is_constructibleておらず、定義されていない場合はそのstd::has_trivial_constructor<T>意志がバーフになります。Tおそらく、std::is_constructible到着するとそれに続きます。さらに、最終的には、 GCCでさえもT提供するためのプライベートコンストラクターのみが存在しますstd::is_constructible(これは眉を上げることです)。

が定義されている場合、デストラクタTが必要であり、デストラクタは1つだけです。そして、そのデストラクタは、の他の可能なメンバーよりも公開される可能性がありTます。その観点から、私たちが行うことができる最も単純で最強の遊びは、の存在のためにSFINAEプローブを作成することですT::~T

このSFINAEプローブTは、通常のメンバー関数があるかどうかを判断するための通常の方法で作成することはできません。SFINAEプローブ関数の「はいオーバーロード」を作成するには、のタイプでmf定義された引数を取ります。デストラクタ(またはコンストラクタ)のアドレスを取得することは許可されていないためです。&T::mf

それにもかかわらず、Tが定義されている場合はT::~T、タイプがあります。これは、 ;の呼び出しに評価される式であるときはいつでもDT生成される必要があります。したがって、型にもなります。これは、原則として、関数のオーバーロードの引数型として指定できます。したがって、次のようにプローブを記述できます(GCC 4.6.3):decltype(dt)dtT::~TDT *

#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>

その引数式の意味は、std::declval<A>().~A()一部の人、具体的にはあいまいな場合がありますstd::declval<A>()。関数テンプレートstd::declval<T>()はで定義され、 (への右辺値参照)<type_traits>を返します。ただし、の引数など、評価されていないコンテキストでのみ呼び出すことができます。したがって、の意味は、ある特定のを呼び出すことです。ここでは、のパブリックコンストラクターが存在する必要がない、またはそれについて知る必要がないため、非常に役立ちます。T&&Tdecltypestd::declval<A>().~A()~A()Astd::declval<A>()T

したがって、「Yesoverload」のSFINAEプローブの引数タイプは次のとおりです。のデストラクタのタイプへのポインタであり、のデストラクタAなどtest<T>(0)のタイプがある場合に備えて、そのオーバーロードと一致します。AAT

手元に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出力の5行目はそれが破壊可能であると言い、最後の行はそれが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
于 2012-05-23T15:11:30.050 に答える
20

この投稿ではまだ満足のいく答えが見つかりませんでした...

マイク・キングハンは正しい答えを始め、賢いことを言いました。

したがって、問題は、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;
};
于 2017-08-09T15:04:16.750 に答える
9

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;

デモ。

于 2014-11-11T23:04:27.257 に答える
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を出力します。残念ながら、どちらの場合も、コンパイルするにはクラスの前方宣言が必要です。それを回避する方法がわかりません。

于 2012-05-23T01:45:54.910 に答える