SFINAEを使用してクラスがC++に存在するかどうかを検出することは可能ですか?可能であれば、どのように?
ライブラリの一部のバージョンによってのみ提供されるクラスがあるとします。SFINAEを使用してクラスが存在するかどうかを検出できるかどうかを知りたいのですが。検出の結果は任意です。たとえば、列挙型定数が存在する場合は1、存在しない場合は0です。
T
宣言されていないクラス型についてコンパイラーに何かを教えてもらうと、コンパイルエラーが発生します。それを回避する方法はありません。
したがって、まだ宣言されていない可能性があるクラスT
が「存在する」かどうかを知りたい場合は、最初に宣言する必要があります。T
T
しかし、それは問題ありません。なぜなら、単に宣言T
するだけでは「存在する」ことにはならないからです。なぜなら、T
存在することによって意味しなければならないことはT
定義されているからです。そして、宣言したT
後、それがすでに定義されているかどうかを判断できる場合は、混乱する必要はありません。
したがって、問題はT
、が定義されたクラスタイプであるかどうかを判断することです。
sizeof(T)
ここでは役に立ちません。T
が未定義の
場合、incomplete type T
エラーが発生します。同様にtypeid(T)
。また、宣言されていない場合でも、宣言されている限り、定義されたタイプであるT *
ため、タイプでSFINAEプローブを作成することは適切ではありません。そして、クラスの宣言が義務付けられているので、それも答えではありません。なぜなら、その宣言は「はい」と言うのに十分だからです。T *
T
T
T
std::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)
dt
T::~T
DT *
#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&&
T
decltype
std::declval<A>().~A()
~A()
A
std::declval<A>()
T
したがって、「Yesoverload」の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
出力の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
この投稿ではまだ満足のいく答えが見つかりませんでした...
マイク・キングハンは正しい答えを始め、賢いことを言いました。
したがって、問題は、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;
};
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;
さて、私はこれを行う方法を見つけたと思いますが、もっと良い方法があるかもしれません。ライブラリの一部のインスタンスに含まれ、他のインスタンスには含まれていないクラス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を出力します。残念ながら、どちらの場合も、コンパイルするにはクラスの前方宣言が必要です。それを回避する方法がわかりません。