特定のメンバー関数がクラスに定義されているかどうかによって動作が変わるテンプレートを作成することは可能ですか?
これが私が書きたいことの簡単な例です。
template<class T>
std::string optionalToString(T* obj)
{
if (FUNCTION_EXISTS(T->toString))
return obj->toString();
else
return "toString not defined";
}
したがって、class T
にtoString()
が定義されている場合は、それを使用します。そうでなければ、そうではありません。私がどうしたらよいかわからない魔法の部分は、 "FUNCTION_EXISTS"部分です。
はい、SFINAEを使えば、特定のクラスが特定のメソッドを提供しているかどうかを確認できます。これが実用的なコードです:
#include <iostream>
struct Hello
{
int helloworld() { return 0; }
};
struct Generic {};
// SFINAE test
template <typename T>
class has_helloworld
{
typedef char one;
struct two { char x[2]; };
template <typename C> static one test( typeof(&C::helloworld) ) ;
template <typename C> static two test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(char) };
};
int main(int argc, char *argv[])
{
std::cout << has_helloworld<Hello>::value << std::endl;
std::cout << has_helloworld<Generic>::value << std::endl;
return 0;
}
Linuxとgcc 4.1/4.3でテストしたところです。異なるコンパイラを実行している他のプラットフォームに移植可能かどうかはわかりません。
この質問は古いですが、C++ 11では、SFINAEに再び依存して、関数の存在(または実際には型以外のメンバーの存在)をチェックするための新しい方法を得ました。
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
-> decltype(os << obj, void())
{
os << obj;
}
template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
-> decltype(obj.stream(os), void())
{
obj.stream(os);
}
template<class T>
auto serialize(std::ostream& os, T const& obj)
-> decltype(serialize_imp(os, obj, 0), void())
{
serialize_imp(os, obj, 0);
}
それでは、いくつか説明を加えましょう。 decltype
内の最初の式が無効な場合(別名、関数が存在しない場合)、まず最初に expression SFINAE を使用してserialize(_imp)
関数をオーバーロード解決から除外します。
void()
は、それらすべての関数の戻り型をvoid
にするために使用されます。
引数0
は、両方が利用可能であればos << obj
オーバーロードを優先するために使用されます(リテラル0
はint
型であるため、最初のオーバーロードがより適しています)。
さて、あなたはおそらく関数が存在するかどうかをチェックするトレイトが欲しいでしょう。幸い、それを書くのは簡単です。ただし、必要な関数名ごとにトレイトを自分で作成する必要があることに注意してください。
#include <type_traits>
template<class>
struct sfinae_true : std::true_type{};
namespace detail{
template<class T, class A0>
static auto test_stream(int)
-> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
template<class, class A0>
static auto test_stream(long) -> std::false_type;
} // detail::
template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};
そして説明に。まず、sfinae_true
はヘルパー型で、基本的にはdecltype(void(std::declval<T>().stream(a0)), std::true_type{})
を書くのと同じです。利点はそれがより短いということです。
次に、struct has_stream : decltype(...)
は、最後にstd::true_type
またはstd::false_type
から継承します。これは、test_stream
のdecltype
チェックが失敗したかどうかによって異なります。
最後に、std::declval
を渡すと、どのような種類の「値」を得ることができますか。作成方法を知らなくてもかまいません。これはdecltype
、sizeof
などの未評価のコンテキスト内でのみ可能です。
decltype
(およびすべての未評価のコンテキスト)がこの機能強化を得たため、sizeof
は必ずしも必要ではありません。それはdecltype
がすでに型を提供しているということであり、それ自体がよりクリーンです。これは、オーバーロードの1つのsizeof
バージョンです。
template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
int(*)[sizeof((os << obj),0)] = 0)
{
os << obj;
}
int
およびlong
パラメーターは、同じ理由でまだ存在します。配列ポインタは、sizeof
を使用できるコンテキストを提供するために使用されます。
C++では SFINAE を使うことができます(C++ 11の機能ではほぼ任意の式で拡張されたSFINAEをサポートしているのでこれがより簡単です - 一般的なC++で動作するように作られました03コンパイラ):
#define HAS_MEM_FUNC(func, name) \
template<typename T, typename Sign> \
struct name { \
typedef char yes[1]; \
typedef char no [2]; \
template <typename U, U> struct type_check; \
template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
template <typename > static no &chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
上記のテンプレートとマクロは、テンプレートにインスタンス化を試み、それにメンバー関数ポインタ型と実際のメンバー関数ポインタを与えます。型が合わない場合、SFINAEはテンプレートを無視します。使い方はこんな感じ:
HAS_MEM_FUNC(toString, has_to_string);
template<typename T> void
doSomething() {
if(has_to_string<T, std::string(T::*)()>::value) {
...
} else {
...
}
}
ただし、そのif分岐内でそのtoString
関数を呼び出すことはできません。コンパイラは両方のブランチで妥当性をチェックするため、関数が存在しない場合は失敗します。 1つの方法はもう一度SFINAEを使うことです(enable_ifも後押しから得ることができます):
template<bool C, typename T = void>
struct enable_if {
typedef T type;
};
template<typename T>
struct enable_if<false, T> { };
HAS_MEM_FUNC(toString, has_to_string);
template<typename T>
typename enable_if<has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T has toString ... */
return t->toString();
}
template<typename T>
typename enable_if<!has_to_string<T,
std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
/* something when T doesnt have toString ... */
return "T::toString() does not exist.";
}
それを使って楽しんでください。その利点は、それがオーバーロードされたメンバ関数、そしてconstメンバ関数に対しても働くことです(メンバ関数のポインタ型としてstd::string(T::*)() const
を使うことを忘れないでください!)。
この質問は2歳ですが、私は私の答えを追加することを敢えてします。うまくいけば、それは前の、明らかに優れた、解決策を明確にするでしょう。私はNicola BonelliとJohannes Schaubの非常に有用な答えを取り、それらを私見、読みやすく、明確でtypeof
拡張子を必要としないソリューションに統合しました。
template <class Type>
class TypeHasToString
{
// This type won't compile if the second template parameter isn't of type T,
// so I can put a function pointer type in the first parameter and the function
// itself in the second thus checking that the function has a specific signature.
template <typename T, T> struct TypeCheck;
typedef char Yes;
typedef long No;
// A helper struct to hold the declaration of the function pointer.
// Change it if the function signature changes.
template <typename T> struct ToString
{
typedef void (T::*fptr)();
};
template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
template <typename T> static No HasToString(...);
public:
static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};
私はそれをgcc 4.1.2で調べました。クレジットは主にNicola BonelliとJohannes Schaubにかかっているので、私の答えがあなたを助けるなら彼らに投票してください:)
requires
式C++ 20には、 requires
expressions のような概念とさまざまなツールが付属しています。これらは、関数の存在を確認するための組み込みの方法です。 tehmを使えば、optionalToString
関数を次のように書き換えることができます。
template<class T>
std::string optionalToString(T* obj)
{
constexpr bool has_toString = requires(const T& t) {
t.toString();
};
if constexpr (has_toString)
return obj->toString();
else
return "toString not defined";
}
N4502 はC++ 17標準ライブラリに含めるための検出方法を提案しています。これはやや洗練された方法で問題を解決することができます。さらに、それはちょうど図書館の基礎TS v2に受け入れられました。 std::is_detected
を含むいくつかのメタ関数が導入されています。これを使って、その上に型や関数の検出メタ関数を簡単に書くことができます。使い方は次のとおりです。
template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );
template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;
上記の例はテストされていないことに注意してください。検出ツールキットは標準ライブラリではまだ利用できませんが、この提案には完全に実装されており、本当に必要な場合は簡単にコピーできます。これは、C++ 17の機能if constexpr
と相性がいいです。
template<class T>
std::string optionalToString(T* obj)
{
if constexpr (has_toString<T>)
return obj->toString();
else
return "toString not defined";
}
そのようなチェックを実行するためのもう1つのやや慣用的なツールキット - それほどエレガントではありませんが - Boost.TTI 、Boost 1.54.0で導入されました。あなたの例では、マクロBOOST_TTI_HAS_MEMBER_FUNCTION
を使う必要があります。使い方は次のとおりです。
#include <boost/tti/has_member_function.hpp>
// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)
// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;
それから、bool
を使用してSFINAEチェックを作成できます。
説明
マクロBOOST_TTI_HAS_MEMBER_FUNCTION
は、最初のテンプレートパラメータとしてチェックされた型をとるメタ関数has_member_function_toString
を生成します。 2番目のテンプレートパラメータはメンバ関数の戻り値の型に対応し、以下のパラメータは関数のパラメータの型に対応します。クラスvalue
にメンバー関数std::string toString()
がある場合、メンバーtrue
にはT
が含まれます。
あるいは、has_member_function_toString
は、テンプレート関数としてメンバ関数ポインタを受け取ることができます。したがって、has_member_function_toString<T, std::string>::value
をhas_member_function_toString<std::string T::* ()>::value
に置き換えることが可能です。
これがタイプ特性が存在するものです。残念ながら、それらは手動で定義する必要があります。あなたの場合は、次のように想像してください。
template <typename T>
struct response_trait {
static bool const has_tostring = false;
};
template <>
struct response_trait<your_type_with_tostring> {
static bool const has_tostring = true;
}
C++ 11のための簡単な解決策:
template<class T>
auto optionalToString(T* obj)
-> decltype( obj->toString() )
{
return obj->toString();
}
auto optionalToString(...) -> string
{
return "toString not defined";
}
3年後の更新:(そしてこれはテストされていません)存在をテストするために、私はこれがうまくいくと思います:
template<class T>
constexpr auto test_has_toString_method(T* obj)
-> decltype( obj->toString() , std::true_type{} )
{
return obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
return "toString not defined";
}
さて、この質問にはすでに多くの回答がありますが、Morwennからのコメントを強調したいと思います。C++ 17の提案があり、それが本当に簡単になります。詳細については、 N4502 を参照してください。ただし、自己完結型の例として、次の点を考慮してください。
この部分は定数部分です。ヘッダーに入れてください。
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
それからあなたが探しているもの(型、メンバ型、関数、メンバ関数など)を指定する変数部分があります。 OPの場合:
template <typename T>
using toString_t = decltype(std::declval<T>().toString());
template <typename T>
using has_toString = detect<T, toString_t>;
N4502 から抜粋した次の例は、より複雑なプローブを示しています。
// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())
// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;
上記の他の実装と比較すると、これは非常に簡単です。少数のツール(void_t
およびdetect
)で十分で、毛深いマクロは不要です。その上、以前の方法よりもかなり効率的(コンパイル時およびコンパイラのメモリ消費量)であることが報告されました( N4502 を参照)。
これが 実例 です。 Clangでも問題なく動作しますが、残念ながら、5.1より前のGCCバージョンではC++ 11標準の解釈が異なるため、void_t
が期待通りに動作しませんでした。 Yakkはすでに次善策を提供しています:void_t
の次の定義を使用してください( パラメータリストのvoid_tは動作しますが戻り値の型としては動作しません ):
#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif
これは、「Xを実行した場合、コンパイルできますか」という一般的な問題に対するC++ 11の解決策です。
template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
T,
type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};
このコンテキストで0個の引数で呼び出すことができるメソッドhas_to_string
がtrue
にある場合に限り、has_to_string<T>::value
がT
になるように.toString
を特性決定します。
次に、タグディスパッチを使用します。
namespace details {
template<class T>
std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
return obj->toString();
}
template<class T>
std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
return "toString not defined";
}
}
template<class T>
std::string optionalToString(T* obj) {
return details::optionalToString_helper( obj, has_to_string<T>{} );
}
これは複雑なSFINAE式より保守しやすい傾向があります。
あなたがあなたがそれをたくさんやっていると思うなら、あなたはマクロでこれらのトレイトを書くことができます、しかしそれらは比較的単純である(それぞれ数行)ので、おそらくそれの価値がないでしょう:
#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};
上記の動作はマクロMAKE_CODE_TRAIT
を作成することです。あなたはそれにあなたが欲しい特性の名前と、タイプT
をテストすることができるいくつかのコードを渡します。したがって:
MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )
上記の特性クラスを作成します。
余談ですが、上記の手法はMSが "expression SFINAE"と呼ぶものの一部であり、2013年のコンパイラはかなり困難に失敗しています。
C++ 1yでは、次の構文が可能です。
template<class T>
std::string optionalToString(T* obj) {
return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
return obj.toString();
}) *compiled_else ([&]{
return "toString not defined";
});
}
これは多くのC++機能を悪用するインラインコンパイルの条件付きブランチです。 (コードがインラインであることによる)利点は(それがどのように機能するのかを理解している人を除く)コストに見合うものではないので、そうすることはおそらく価値がありません。
ここにいくつかの用法スニペットがあります:*これらすべての根拠はさらに遠くにあります
特定のクラスのメンバx
を確認します。var、func、class、union、enumのいずれかです。
CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;
メンバー関数void x()
:を確認してください
//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;
メンバー変数x
:を確認してください
CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;
メンバークラスx
:を確認してください
CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;
メンバーの和集合x
:を確認してください
CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;
メンバーenum x
:を確認してください
CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;
シグネチャに関係なく、メンバー関数x
を確認してください。
CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
OR
CREATE_MEMBER_CHECKS(x); //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;
詳細とコア:
/*
- Multiple inheritance forces ambiguity of member names.
- SFINAE is used to make aliases to member names.
- Expression SFINAE is used in just one generic has_member that can accept
any alias we pass it.
*/
//Variadic to force ambiguity of class members. C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};
//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};
template<typename A, typename = void>
struct got_type : std::false_type {};
template<typename A>
struct got_type<A> : std::true_type {
typedef A type;
};
template<typename T, T>
struct sig_check : std::true_type {};
template<typename Alias, typename AmbiguitySeed>
struct has_member {
template<typename C> static char ((&f(decltype(&C::value))))[1];
template<typename C> static char ((&f(...)))[2];
//Make sure the member name is consistently spelled the same.
static_assert(
(sizeof(f<AmbiguitySeed>(0)) == 1)
, "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
);
static bool const value = sizeof(f<Alias>(0)) == 2;
};
マクロ(El Diablo!):
CREATE_MEMBER_CHECK:
//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member) \
\
template<typename T, typename = std::true_type> \
struct Alias_##member; \
\
template<typename T> \
struct Alias_##member < \
T, std::integral_constant<bool, got_type<decltype(&T::member)>::value> \
> { static const decltype(&T::member) value; }; \
\
struct AmbiguitySeed_##member { char member; }; \
\
template<typename T> \
struct has_member_##member { \
static const bool value \
= has_member< \
Alias_##member<ambiguate<T, AmbiguitySeed_##member>> \
, Alias_##member<AmbiguitySeed_##member> \
>::value \
; \
}
CREATE_MEMBER_VAR_CHECK:
//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_var_##var_name : std::false_type {}; \
\
template<typename T> \
struct has_member_var_##var_name< \
T \
, std::integral_constant< \
bool \
, !std::is_member_function_pointer<decltype(&T::var_name)>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_SIG_CHECK:
//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix) \
\
template<typename T, typename = std::true_type> \
struct has_member_func_##templ_postfix : std::false_type {}; \
\
template<typename T> \
struct has_member_func_##templ_postfix< \
T, std::integral_constant< \
bool \
, sig_check<func_sig, &T::func_name>::value \
> \
> : std::true_type {}
CREATE_MEMBER_CLASS_CHECK:
//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_class_##class_name : std::false_type {}; \
\
template<typename T> \
struct has_member_class_##class_name< \
T \
, std::integral_constant< \
bool \
, std::is_class< \
typename got_type<typename T::class_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_UNION_CHECK:
//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_union_##union_name : std::false_type {}; \
\
template<typename T> \
struct has_member_union_##union_name< \
T \
, std::integral_constant< \
bool \
, std::is_union< \
typename got_type<typename T::union_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_ENUM_CHECK:
//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name) \
\
template<typename T, typename = std::true_type> \
struct has_member_enum_##enum_name : std::false_type {}; \
\
template<typename T> \
struct has_member_enum_##enum_name< \
T \
, std::integral_constant< \
bool \
, std::is_enum< \
typename got_type<typename T::enum_name>::type \
>::value \
> \
> : std::true_type {}
CREATE_MEMBER_FUNC_CHECK:
//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func) \
template<typename T> \
struct has_member_func_##func { \
static const bool value \
= has_member_##func<T>::value \
&& !has_member_var_##func<T>::value \
&& !has_member_class_##func<T>::value \
&& !has_member_union_##func<T>::value \
&& !has_member_enum_##func<T>::value \
; \
}
CREATE_MEMBER_CHECKS:
//Create all the checks for one member. Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member) \
CREATE_MEMBER_CHECK(member); \
CREATE_MEMBER_VAR_CHECK(member); \
CREATE_MEMBER_CLASS_CHECK(member); \
CREATE_MEMBER_UNION_CHECK(member); \
CREATE_MEMBER_ENUM_CHECK(member); \
CREATE_MEMBER_FUNC_CHECK(member)
私はこれに対する答えを別のスレッドで書きました。(上記の解決策とは異なり)継承されたメンバ関数もチェックします。
その解決策の例をいくつか示します。
次のシグネチャを持つメンバーをチェックしています:T::const_iterator begin() const
template<class T> struct has_const_begin
{
typedef char (&Yes)[1];
typedef char (&No)[2];
template<class U>
static Yes test(U const * data,
typename std::enable_if<std::is_same<
typename U::const_iterator,
decltype(data->begin())
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};
メソッドの安定性もチェックされ、プリミティブ型でも同様に機能します。 (has_const_begin<int>::value
はfalseでコンパイル時エラーは発生しません)
今、私たちは署名を探しています:void foo(MyClass&, unsigned)
template<class T> struct has_foo
{
typedef char (&Yes)[1];
typedef char (&No)[2];
template<class U>
static Yes test(U * data, MyClass* arg1 = 0,
typename std::enable_if<std::is_void<
decltype(data->foo(*arg1, 1u))
>::value>::type * = 0);
static No test(...);
static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};
MyClassはデフォルトで構築可能である必要も、特別な概念を満たす必要もないことに注意してください。このテクニックはテンプレートメンバーにも同様に機能します。
私はこれに関する意見を熱心に待っています。
このメソッドが基本クラスで定義されていると、litbによってここに提示されている標準C++ソリューションは期待通りに動作しません。
この状況に対処する解決策については、以下を参照してください。
ロシア語の場合--- http://www.rsdn.ru/forum/message/2759773.1.aspx
Roman.Perepelitsaによる英語翻訳: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1
それはめちゃくちゃ賢いです。しかしながら、この解決策に伴う1つの問題は、テストされている型が基本クラスとして使用できないもの(例えばプリミティブ型)である場合、コンパイラエラーを引き起こすことです。
Visual Studioでは、引数を持たないメソッドで作業する場合、sizeof式のdeduce()の引数の周りに余分なペアのredundant()を挿入する必要があることに気付きました。
これはNiceの小さなパズルでした。素晴らしい質問です。
これは Nicola Bonelliの解決策 に代わるもので、非標準のtypeof
演算子に依存しません。
残念ながら、GCC(MinGW)3.4.5またはDigital Mars 8.42nでは機能しませんが、MSVCのすべてのバージョン(VC6を含む)およびComeau C++では機能します。
長いコメントブロックには、それがどのように機能するか(または機能することが想定されている)に関する詳細があります。それが言うように、私はどの振る舞いが規格に準拠しているかわからない - それについてのコメントを歓迎する。
更新 - 2008年11月7日:
このコードは構文的には正しいのですが、MSVCとComeau C++が示す動作は標準には従っていません(右側に私を指し示している Leon Timmermans および litb に感謝します)。方向)。 C++ 03標準は次のように述べています。
14.6.2従属名[temp.dep]
第3項
クラステンプレートまたはクラステンプレートのメンバーの定義で、クラステンプレートの基本クラスがtemplate-parameterに依存している場合、クラスの定義時にも非修飾名の検索時にも基本クラスのスコープは調べられません。テンプレートまたはメンバー、あるいはクラステンプレートまたはメンバーのインスタンス化中.
そのため、MSVCまたはComeauが、テンプレートのインスタンス化時にtoString()
の呼び出しサイトで名前検索を実行するT
のdoToString()
メンバー関数を考慮すると、これは正しくありません(実際にはこの場合は探していた動作ですが)。 。
GCCとDigital Marsの動作は正しいように見えます - どちらの場合も、非メンバtoString()
関数は呼び出しにバインドされています。
ラット - 私は私が賢い解決策を見つけたかもしれないと思いました、代わりに私はカップルのコンパイラバグを発見しました...
#include <iostream>
#include <string>
struct Hello
{
std::string toString() {
return "Hello";
}
};
struct Generic {};
// the following namespace keeps the toString() method out of
// most everything - except the other stuff in this
// compilation unit
namespace {
std::string toString()
{
return "toString not defined";
}
template <typename T>
class optionalToStringImpl : public T
{
public:
std::string doToString() {
// in theory, the name lookup for this call to
// toString() should find the toString() in
// the base class T if one exists, but if one
// doesn't exist in the base class, it'll
// find the free toString() function in
// the private namespace.
//
// This theory works for MSVC (all versions
// from VC6 to VC9) and Comeau C++, but
// does not work with MinGW 3.4.5 or
// Digital Mars 8.42n
//
// I'm honestly not sure what the standard says
// is the correct behavior here - it's sort
// of like ADL (Argument Dependent Lookup -
// also known as Koenig Lookup) but without
// arguments (except the implied "this" pointer)
return toString();
}
};
}
template <typename T>
std::string optionalToString(T & obj)
{
// ugly, hacky cast...
optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);
return temp->doToString();
}
int
main(int argc, char *argv[])
{
Hello helloObj;
Generic genericObj;
std::cout << optionalToString( helloObj) << std::endl;
std::cout << optionalToString( genericObj) << std::endl;
return 0;
}
私は https://stackoverflow.com/a/264088/2712152 で提供されている解決法をもう少し一般的にするために修正しました。また、C++ 11の新機能は使用していないので、古いコンパイラでも使用でき、msvcでも機能します。しかし、C99は可変長マクロを使用しているので、コンパイラーはこれを使用できるようにする必要があります。
次のマクロは、特定のクラスが特定のtypedefを持っているかどうかを確認するために使用できます。
/**
* @class : HAS_TYPEDEF
* @brief : This macro will be used to check if a class has a particular
* typedef or not.
* @param typedef_name : Name of Typedef
* @param name : Name of struct which is going to be run the test for
* the given particular typedef specified in typedef_name
*/
#define HAS_TYPEDEF(typedef_name, name) \
template <typename T> \
struct name { \
typedef char yes[1]; \
typedef char no[2]; \
template <typename U> \
struct type_check; \
template <typename _1> \
static yes& chk(type_check<typename _1::typedef_name>*); \
template <typename> \
static no& chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
次のマクロは、特定のクラスが特定のメンバ関数を持っているかどうか、または任意の数の引数を持たないかどうかを確認するために使用できます。
/**
* @class : HAS_MEM_FUNC
* @brief : This macro will be used to check if a class has a particular
* member function implemented in the public section or not.
* @param func : Name of Member Function
* @param name : Name of struct which is going to be run the test for
* the given particular member function name specified in func
* @param return_type: Return type of the member function
* @param Ellipsis(...) : Since this is macro should provide test case for every
* possible member function we use variadic macros to cover all possibilities
*/
#define HAS_MEM_FUNC(func, name, return_type, ...) \
template <typename T> \
struct name { \
typedef return_type (T::*Sign)(__VA_ARGS__); \
typedef char yes[1]; \
typedef char no[2]; \
template <typename U, U> \
struct type_check; \
template <typename _1> \
static yes& chk(type_check<Sign, &_1::func>*); \
template <typename> \
static no& chk(...); \
static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
}
上記の2つのマクロを使って、has_typedefとhas_mem_funcのチェックを以下のように実行できます。
class A {
public:
typedef int check;
void check_function() {}
};
class B {
public:
void hello(int a, double b) {}
void hello() {}
};
HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);
int main() {
std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}
Has_foo
概念検査を書くことによって、SFINAEとテンプレート部分特殊化を使った例:
#include <type_traits>
struct A{};
struct B{ int foo(int a, int b);};
struct C{void foo(int a, int b);};
struct D{int foo();};
struct E: public B{};
// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;
template<typename T, typename = void> struct Has_foo: std::false_type{};
template<typename T>
struct Has_foo<T, void_t<
std::enable_if_t<
std::is_same<
int,
decltype(std::declval<T>().foo((int)0, (int)0))
>::value
>
>>: std::true_type{};
static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");
何らかの「機能」が型によってサポートされているかどうかをチェックするために使用できる汎用テンプレート。
#include <type_traits>
template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
// these structs are used to recognize which version
// of the two functions was chosen during overload resolution
struct supported {};
struct not_supported {};
// this overload of chk will be ignored by SFINAE principle
// if TypeChecker<Type_> is invalid type
template <typename Type_>
static supported chk(typename std::decay<TypeChecker<Type_>>::type *);
// Ellipsis has the lowest conversion rank, so this overload will be
// chosen during overload resolution only if the template overload above is ignored
template <typename Type_>
static not_supported chk(...);
// if the template overload of chk is chosen during
// overload resolution then the feature is supported
// if the ellipses overload is chosen the the feature is not supported
static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};
シグニチャdouble(const char*)
と互換性のあるメソッドfoo
があるかどうかを確認するテンプレート
// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));
例
// types that support has_foo
struct struct1 { double foo(const char*); }; // exact signature match
struct struct2 { int foo(const std::string &str); }; // compatible signature
struct struct3 { float foo(...); }; // compatible Ellipsis signature
struct struct4 { template <typename T>
int foo(T t); }; // compatible template signature
// types that do not support has_foo
struct struct5 { void foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double foo( int *); }; // const char* can't be converted to int*
struct struct8 { double bar(const char*); }; // there is no foo method
int main()
{
std::cout << std::boolalpha;
std::cout << is_supported<has_foo, int >::value << std::endl; // false
std::cout << is_supported<has_foo, double >::value << std::endl; // false
std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
std::cout << is_supported<has_foo, struct4>::value << std::endl; // true
std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
std::cout << is_supported<has_foo, struct8>::value << std::endl; // false
return 0;
}
このサイトで私が一度見たことのある次のような素敵なトリックは誰にも示唆されていません。
template <class T>
struct has_foo
{
struct S { void foo(...); };
struct derived : S, T {};
template <typename V, V> struct W {};
template <typename X>
char (&test(W<void (X::*)(), &X::foo> *))[1];
template <typename>
char (&test(...))[2];
static const bool value = sizeof(test<derived>(0)) == 1;
};
あなたはTがクラスであることを確認しなければなりません。 fooの検索のあいまいさは代入の失敗だと思われます。私はそれが標準であるかどうかわからない、gccで動作するようにしました。
ここにはたくさんの答えがありますが、私は本当のメソッド解決の順序付けを行いながら、新しいc ++を使わないバージョンを見つけることに失敗しました。機能(c ++ 98機能のみを使用)。
注:このバージョンはvc ++ 2013、g ++ 5.2.0、およびオンラインコンパイラでテストされ、動作しています。
それで、私は、sizeof()だけを使うバージョンを思いつきました:
template<typename T> T declval(void);
struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);
struct yes { char v[1]; };
struct no { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};
template<typename T>
struct has_awesome_member {
template<typename U> static yes_no<(sizeof((
declval<U>().awesome_member(),fake_void()
))!=0)> check(int);
template<typename> static no check(...);
enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};
struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };
static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");
ライブデモ(拡張戻り型チェックとvc ++ 2010回避策付き): http://cpp.sh/5b2vs
私は自分でそれを思い付いたので、情報源はありません。
G ++コンパイラでLiveデモを実行するときは、0の配列サイズが許可されていることに注意してください。つまり、static_assertを使用しても、失敗してもコンパイラエラーは発生しません。
よく使用される回避策は、マクロ内の 'typedef'を 'extern'に置き換えることです。
これは私のバージョンで、テンプレートメンバ関数を含む、おそらくデフォルトの引数を含む、任意のアリティを持つすべての可能なメンバ関数オーバーロードを処理します。メンバー関数があるクラス型を指定されたarg型で呼び出すとき、3つの相互に排他的なシナリオを区別します。(1)有効、(2)あいまい、または(3)実行不可能です。使用例
#include <string>
#include <vector>
HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)
struct test
{
void bar(int);
void bar(double);
void bar(int,double);
template < typename T >
typename std::enable_if< not std::is_integral<T>::value >::type
bar(const T&, int=0){}
template < typename T >
typename std::enable_if< std::is_integral<T>::value >::type
bar(const std::vector<T>&, T*){}
template < typename T >
int bar(const std::string&, int){}
};
今、あなたはこのようにそれを使うことができます:
int main(int argc, const char * argv[])
{
static_assert( has_mem_bar<test>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");
static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");
static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");
static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");
static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");
return 0;
}
これはc ++ 11で書かれたコードですが、(マイナーな調整で)それをtypeof拡張子(例えばgcc)を持つ非c ++ 11に簡単に移植することができます。 HAS_MEMマクロを自分のものに置き換えることができます。
#pragma once
#if __cplusplus >= 201103
#include <utility>
#include <type_traits>
#define HAS_MEM(mem) \
\
template < typename T > \
struct has_mem_##mem \
{ \
struct yes {}; \
struct no {}; \
\
struct ambiguate_seed { char mem; }; \
template < typename U > struct ambiguate : U, ambiguate_seed {}; \
\
template < typename U, typename = decltype(&U::mem) > static constexpr no test(int); \
template < typename > static constexpr yes test(...); \
\
static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ; \
typedef std::integral_constant<bool,value> type; \
};
#define HAS_MEM_FUN_CALL(memfun) \
\
template < typename Signature > \
struct has_valid_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_valid_mem_fun_call_##memfun< T(Args...) > \
{ \
struct yes {}; \
struct no {}; \
\
template < typename U, bool = has_mem_##memfun<U>::value > \
struct impl \
{ \
template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
struct test_result { using type = yes; }; \
\
template < typename V > static constexpr typename test_result<V>::type test(int); \
template < typename > static constexpr no test(...); \
\
static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename U > \
struct impl<U,false> : std::false_type {}; \
\
static constexpr bool value = impl<T>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_ambiguous_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) > \
{ \
struct ambiguate_seed { void memfun(...); }; \
\
template < class U, bool = has_mem_##memfun<U>::value > \
struct ambiguate : U, ambiguate_seed \
{ \
using ambiguate_seed::memfun; \
using U::memfun; \
}; \
\
template < class U > \
struct ambiguate<U,false> : ambiguate_seed {}; \
\
static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_viable_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_viable_mem_fun_call_##memfun< T(Args...) > \
{ \
static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value \
or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct has_no_viable_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) > \
{ \
static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value; \
using type = std::integral_constant<bool, value>; \
}; \
\
template < typename Signature > \
struct result_of_mem_fun_call_##memfun; \
\
template < typename T, typename... Args > \
struct result_of_mem_fun_call_##memfun< T(Args...) > \
{ \
using type = decltype(std::declval<T>().memfun(std::declval<Args>()...)); \
};
#endif
この解決策はどうですか?
#include <type_traits>
template <typename U, typename = void> struct hasToString : std::false_type { };
template <typename U>
struct hasToString<U,
typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };
C++ 14ではすべてのメタプログラミングをスキップでき、 Fit ライブラリの fit::conditional
を使ってこれを書くことができます。
template<class T>
std::string optionalToString(T* x)
{
return fit::conditional(
[](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
[](auto*) { return "toString not defined"; }
)(x);
}
同様に、ラムダから直接関数を作成することもできます。
FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
[](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
[](auto*) -> std::string { return "toString not defined"; }
);
ただし、一般的なラムダをサポートしていないコンパイラを使用している場合は、別の関数オブジェクトを作成する必要があります。
struct withToString
{
template<class T>
auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
{
return obj->toString();
}
};
struct withoutToString
{
template<class T>
std::string operator()(T*) const
{
return "toString not defined";
}
};
FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
withToString(),
withoutToString()
);
私は同様の問題を抱えていました:
いくつかの基本クラスから派生する可能性があるテンプレートクラス、特定のメンバーを持つもの、および持たないものがあります。
私はそれを "typeof"(Nicola Bonelliの)答えと同じように解決しましたが、decltypeを使ってMSVS上でコンパイルして正しく実行します。
#include <iostream>
#include <string>
struct Generic {};
struct HasMember
{
HasMember() : _a(1) {};
int _a;
};
// SFINAE test
template <typename T>
class S : public T
{
public:
std::string foo (std::string b)
{
return foo2<T>(b,0);
}
protected:
template <typename T> std::string foo2 (std::string b, decltype (T::_a))
{
return b + std::to_string(T::_a);
}
template <typename T> std::string foo2 (std::string b, ...)
{
return b + "No";
}
};
int main(int argc, char *argv[])
{
S<HasMember> d1;
S<Generic> d2;
std::cout << d1.foo("HasMember: ") << std::endl;
std::cout << d2.foo("Generic: ") << std::endl;
return 0;
}
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
return obj->toString();
}
template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
throw "Error!";
}
これが作業コードの例です。
template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());
template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
return obj->toString();
}
template <class T>
std::string optionalToString(const T* obj, long)
{
return "toString not defined";
}
int main()
{
A* a;
B* b;
std::cout << optionalToString(a, 0) << std::endl; // This is A
std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}
toStringFn<T>* = nullptr
は、0
で呼び出されたときにint
を取る関数よりも優先される、追加のlong
引数を取る関数を有効にします。
関数が実装されている場合はtrue
を返す関数にも同じ原則を使用できます。
template <typename T>
constexpr bool toStringExists(long)
{
return false;
}
template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
return true;
}
int main()
{
A* a;
B* b;
std::cout << toStringExists<A>(0) << std::endl; // true
std::cout << toStringExists<B>(0) << std::endl; // false
}