クラスメンバー関数の存在のテンプレートチェック?


498

クラスで特定のメンバー関数が定義されているかどうかに応じて動作を変更するテンプレートを作成することは可能ですか?

これが私が書きたいことの簡単な例です:

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」部分です。


6
もちろん、以下のテンプレートの回答はコンパイル時の情報でのみ機能することは言うまでもありません。つまり、TにはtoStringが必要です。あなたがTのサブクラスに渡す場合ないのtoStringを定義しますが、Tがないではない、あなたはのtoStringが定義されていないと言われるだろう。
アリスパーセル

可能性の重複メンバー名(変数や関数)がクラスに存在する場合またはタイプを指定せずに、確認する方法は?、C ++ 03からC ++ 1yまでのより広範な問題をカバーするため。
iammilind 2016年

回答:


319

はい、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でテストしたところです。異なるコンパイラを実行している他のプラットフォームに移植可能かどうかはわかりません。


18
ただし、「1つ」と「2つ」には次のものを使用しました。typedefchar Small; プラットフォームBig {char dummy [2];}を使用して、プラットフォームに依存する変数のサイズについて曖昧さがないようにします。
user23167 2008年

6
sizeof(char)== sizeof(long)のプラットフォームが地球上に存在することは疑わしい
Nicola Bonelli

17
完全にはわかりませんが、これは移植性があるとは思いません。typeofはGCC拡張であり、これは他のコンパイラでは機能しません。
Leon Timmermans、

56
typeofは必要ありません-char [sizeof(&C :: helloworld)]も機能します。また、sizeof(long)== sizeof(char)を回避するには、struct {char [2]};を使用します。サイズが2以上である必要があります
MSalters 2009

57
些細なことですが、理解するのに少し時間がかかりました。たとえば、-std = c ++ 0xを介してC ++ 0xを使用typeofするdecltype場合は、置き換えてください。
hr

264

この質問は古くなっていますが、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);
}

いくつかの説明に移ります。最初に、式SFINAEを使用してserialize(_imp)関数をオーバーロードの解決から除外します。内部の最初の式decltypeが有効でない場合(つまり、関数が存在しない場合)です。

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最後にが継承されます。 最後に、どのように構築するかを知る必要なく、渡したタイプの「値」を提供します。このような、未評価のコンテキスト内部でのみ可能であることに注意してください、そして他のもの。decltypetest_stream
std::declvaldecltypesizeof


(およびすべての評価さdecltypeれていないsizeofコンテキスト)がその拡張機能を得たので、必ずしも必要ではないことに注意してください。それはdecltypeすでにタイプを提供しているだけであり、それ自体がよりクリーンです。以下sizeofは、オーバーロードの1つのバージョンです。

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

パラメータは同じ理由でまだそこにあります。配列ポインターは、使用可能なコンテキストを提供するために使用されます。intlongsizeof


4
decltype以上の利点はsizeof、関数呼び出しの特別に細工された規則によって一時変数が導入されないことです(そのため、戻り値の型のデストラクタへのアクセス権を持っている必要はなく、戻り値の型が暗黙的なインスタンス化を引き起こしません)クラステンプレートのインスタンス化)。
ヨハネスシャウブ-litb 2014

5
MicrosoftはまだC ++コンパイラにExpression SFINAEを実装していません。これがうまくいかなかった理由を混乱させたので、一部の人の時間を節約するのに役立つかもしれません。素晴らしい解決策ですが、Visual Studioでの使用を待ちきれません。
ジョナサン

3
最初のリンクの例は壊れています
NathanOliver

1
static_assert(has_stream<X, char>() == true, "fail X");charはintに変換可能であるため、コンパイルされ、アサートされないので、その動作が必要でなく、すべての引数の型が一致する場合、どのようにしてそれを実現できるかわかりません。
ガブリエル

4
decltypeの2つの引数について私がそうであるように困惑している場合:decltypeは実際には1つだけを受け取ります。ここではコンマが演算子です。参照してくださいstackoverflow.com/questions/16044514/...
アンドレ・

159

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 {
      ...
   }
}

ただしtoString、そのifブランチでその関数を呼び出すだけではできないことに注意してください。コンパイラは両方のブランチで有効性をチェックするため、関数が存在しない場合は失敗します。1つの方法は、SFINAEをもう一度使用することです(enable_ifもboostから取得できます)。

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メンバー関数のポインター型として使用することを忘れないで ください!)。


7
type_check署名が正確に一致するようにするためのの使用方法が気に入っています。署名付きのメソッドを呼び出すことができる方法で呼び出すことができるすべてのメソッドに一致するようにする方法Signはありますか?(たとえば、Sign=の場合std::string(T::*)()std::string T::toString(int default = 42, ...)一致することを許可します。)
j_random_hacker

5
これについてはすぐにはわからないことを理解しているだけなので、他の人を助けるために:chkは定義されていないので、定義する必要はありません。sizeof演算子は、chkを呼び出す必要がない、chkの出力のサイズを決定します。
SCFrench 2011年

3
@ deek0146:はい、TTへのポインターへのポインター宣言はSFINAEの対象ではなく、クラス以外のTに対してエラーになるため、プリミティブ型であってはなりません。IMOの最も簡単な解決策はis_class、ブースト。
Jan Hudec、2012年

2
toStringテンプレート関数の場合、これをどのように機能させることができますか?
フランク

4
これはBoostで(または同等のもの)ですか?
Dan Nissenbaum 2013年

89

C ++ 20- requires

C ++ 20には、概念や、関数の存在をチェックする組み込みの方法であるrequiresなどの各種ツールが付属しています。それらを使用すると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";
}

C ++ 20以前-検出ツールキット

N4502は、C ++ 17標準ライブラリに含めるための検出ツールキットを提案し、最終的にはライブラリの基礎TS v2に組み込まれました。それはそれrequires以来式に組み込まれているため、標準に入る可能性はほとんどありませんが、それでもややエレガントな方法で問題を解決します。ツールキットはいくつかのメタ関数を導入して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";
}

C ++ 14-Boost.Hana

Boost.Hanaは明らかにこの特定の例に基づいて構築されており、ドキュメントにC ++ 14のソリューションを提供しているので、直接引用します。

[...] Hanaは、is_validC ++ 14ジェネリックラムダと組み合わせて同じもののより明確な実装を取得できる関数を提供します。

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

これによりhas_toString、渡された引数に対して指定された式が有効かどうかを返す関数オブジェクトが残ります。結果はとして返されるIntegralConstantので、関数の結果はいずれにせよ型として表されるため、constexpr-nessはここでは問題になりません。さて、それほど冗長ではない(これはワンライナーです!)ことに加えて、意図ははるかに明確です。その他の利点は、has_toStringより高次のアルゴリズムに渡すことができ、関数スコープでも定義できるため、名前空間スコープを実装の詳細で汚染する必要がないという事実です。

Boost.TTI

そのようなチェックを実行するもう少し慣用的なツールキットは、エレガントではありませんが、Boost 1.54.0で導入されたBoost.TTIです。あなたの例では、マクロを使用する必要があります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はが含まれます。trueTstd::string toString()

または、has_member_function_toStringメンバー関数ポインターをテンプレートパラメーターとして使用できます。したがって、交換することが可能であるhas_member_function_toString<T, std::string>::valueことでhas_member_function_toString<std::string T::* ()>::value


1
03よりも簡潔
ZFY

@ZFY Boost.TTIはC ++ 03でも動作すると思いますが、これは多くの中で最もエレガントなソリューションではありません。
モーウェン

C ++ 20ソリューションは本当に有効ですか?私はそれを望みます-しかしそれはg ++とmsvcによって拒否されます-clangによってのみ受け入れられます。
Bernd Baumanns

cppreferenceでは、次のように読むことができます。requires-expressionの要件に無効なタイプまたは式が含まれていて、テンプレート化されたエンティティの宣言内にない場合、プログラムの形式が正しくありません。
Bernd Baumanns

@BerndBaumannsほんと?GCCトランクで動作するようになりました:godbolt.org/z/CBwZdE多分正しいと思います。動作することを確認しただけで、標準の表現に従って合法であるかどうかは確認しませんでした。
Morwenn

56

この質問は2歳ですが、あえて答えを追加します。うまくいけば、それは以前の、議論の余地なく優れた解決策を明らかにするでしょう。私はNicola BonelliとJohannes Schaubの非常に役立つ回答を取り、それらをIMHO、より読みやすく、明確で、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に送られるので、私の答えがあなたを助けるなら、彼らに投票してください:)


1
不思議に思いますが、これは以下のコンラッドルドルフの解決策が行わないことを何ですか?
Alastair Irvine

3
@AlastairIrvine、このソリューションは内部のすべてのロジックを隠します。Konradはユーザーにいくらかの負担をかけます。短くて読みやすいですが、Konradのソリューションでは、を持つクラスごとに個別のテンプレート特殊化が必要toStringです。汎用ライブラリを作成し、そこから任意のクラスを操作したい場合(boostのようなものを考えてください)、ユーザーにいくつかのあいまいなテンプレートの追加の特殊化を定義するように要求することは受け入れられない場合があります。非常に複雑なコードを記述して、パブリックインターフェイスをできるだけシンプルに保つことが望ましい場合があります。
FireAphis 2013

30

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

4
これはシンプルでエレガントですが、厳密に言うとOPの質問には答えません。つまり、呼び出し元が関数の存在を確認できるようにせず、常に提供します。とにかくいい。
エイドリアンW

@AdrianW、良い点。回答を更新しました。私はそれをテストしていません
アーロン・マクダイド

それが他の誰かを助ける場合に備えて、私template<typename>は可変型オーバーロードの前にこの作業を行うことができませんでした:解決のために考慮されていませんでした。
Laboratorio Cobotica

繰り返しますが、これは無効なC ++ 11です。
ピーター

29

これが型の特徴です。残念ながら、それらは手動で定義する必要があります。あなたの場合、次のことを想像してみてください:

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

5
静的定数ではなく特性の列挙型を選択する必要があります: "静的定数メンバーは左辺値であり、これによりコンパイラーは静的メンバーの定義をインスタンス化して割り当てる必要があります。その結果、計算は純粋な「コンパイル時「効果」
Özgür

5
"列挙値は左辺値ではありません(つまり、アドレスがありません)。したがって、「参照によって」渡す場合、静的メモリは使用されません。計算値をリテラルとして渡した場合とほぼ同じです。完全ガイド:これらの考慮事項は、列挙値」C ++のテンプレートを使用するために私達に動機を与える
Özgür

22
Comptrol:いいえ、整数型の静的定数は特殊なケースであるため、ここでの引用は適用されません。ここでは列挙型とまったく同じように動作し、推奨される方法です。古い列挙型ハックは、C ++標準に準拠していないコンパイラでのみ必要でした。
Konrad Rudolph

3
@ロジャー・パテ:違います。ここで「プログラムで使用される」とは、「参照される」と同義語のようです。この一節とすべての最新のC ++コンパイラーによって実装された一節の一般的な読みは、静的定数のを宣言する必要なしに取ることができるということです(前の文はこれを言います:「…メンバーは整数定数式で現れることができます」 …」)。あなたは唯一のあなたは、そのアドレスを取る場合(明示的に介して、それを定義する必要があります&T::xまたは暗黙的に参照にそれを結合することによって)。
Konrad Rudolph


25

さて、この質問にはすでに長い回答リストがありますが、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

非メンバー関数を検出するように拡張することは可能ですか?
プラズマセル16

はい、そうです。例を注意深く見てください。基本的に式を提供し、それが有効かどうかを確認します。この式がメンバー関数呼び出しのみである必要はありません。
akimが

N4502(open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf)は未来への道です...私は型に関するものを検出するきちんとした方法を探していました、そしてN4502は道ですトーゴ。
tlonuk 2017年

11

これは、「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 {};

has_to_stringこのような特性has_to_string<T>::valueは、このコンテキストで引数なしで呼び出すことができるメソッドがあるtrue場合に限ります。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が「式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 ++機能を悪用するインラインコンパイル条件分岐です。(コードがインライン化されていることによる)メリットはコストに見合うものではないため(これが機能することをだれも理解していないことになるため)、そうすることにはおそらく価値がありませんが、上記のソリューションの存在は興味深いかもしれません。


これは私的な事件を扱いますか?
tower120 14年

@ tower120私は実験する必要があります:テンプレートがプライベート/パブリック/保護とどのように相互作用するかは、私には少しあいまいです。has_to_stringただし、どこを起動してもかまいません。
Yakk-Adam Nevraumont 2014年

でも、反対側から見れば... Derivedクラスから保護されたメンバーに到達できます。たぶん、このすべてをINSIDEクラスに入れて、構造体からconstexpr関数に変換すると...
tower120

ここでは、これを見てcoliru.stacked-crooked.com/a/ee94d16e7c07e093私はちょうどそれがconstexprの作ることができない
tower120


10

ここにいくつかの使用法の抜粋があります:*これらすべての根性はさらに下に

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;

メンバー列挙型を確認し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;

または

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)

1
sig_check<func_sig, &T::func_name>無料の関数チェックに変更した場合、なぜかをsig_check<func_sig, &func_name>確認します。チェックしたい関数の名前を示す「宣言されていない識別子」を使用してビルドできないのですか?私はSFINAEがエラーではないことを期待するので、それはメンバーのためだけに行います、なぜ無料の関数のためではないのですか?
v.oddou

フリー関数はクラスや構造体ではないという事実と関係があると思います。メンバーの存在を推定するこの手法は、実際には、C ++の多重継承メカニズムに重点を置いています。これは、チェックしているメンバーをホストする目的でのみ存在するスタブクラスと実際にメンバーをチェックしているクラスの間のあいまいさを強制します。それは興味深い質問ですが、考えていませんでした。他のC ++ 11/14メンバーチェックテクニックをチェックするかもしれませんが、新しい標準でいくつかの賢いことがわかりました。
ブレット・ロッシエ、2015年

あなたの答えをありがとう、私はあなたが継承について与えるあなたの詳細をもっと深くチェックしなければならないかもしれないと思います、なぜなら今まで私はSFINAEに依存して正しいアクセスを表現するのに適切ではない表現を作ることの間の相関関係を見なかったからですテンプレート型パラメーターのメンバー、および多重継承。しかし、私はC ++では遠く離れた概念でさえ相互に出血する可能性があると完全に信じています。今すぐ無料の機能のために、この質問は興味深いです:stackoverflow.com/questions/26744589 TCの答えは、「宣言されていない識別子」を避けるために、ダミーを宣言するのトリックを使用しているようだ
v.oddou

8

(上記のソリューションとは異なり)継承されたメンバー関数もチェックする別のスレッドでこれに対する答えを書きました:

継承されたメンバー関数をチェックするSFINAE

以下はそのソリューションの例です。

例1:

次の署名のあるメンバーをチェックしています: 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であり、コンパイル時エラーは発生しません。)

例2

今、私たちは署名を探しています: 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はデフォルトで構築可能である必要はなく、特別な概念を満たす必要もないことに注意してください。この手法は、テンプレートメンバーでも機能します。

これについてのご意見をお待ちしております。


7

これは素晴らしいパズルでした-素晴らしい質問です!

これは、非標準の演算子に依存しないNicola Bonelliのソリューションの代替案typeofです。

残念ながら、GCC(MinGW)3.4.5またはDigital Mars 8.42nでは動作しませんが、MSVCのすべてのバージョン(VC6を含む)およびComeau C ++では動作します。

長いコメントブロックには、それがどのように機能するか(または機能するはず)の詳細があります。言われているように、どの動作が標準に準拠しているかはわかりません。それについてのコメントをお待ちしています。


アップデート-2008年11月7日:

このコードは構文的には正しいものの、MSVCとComeau C ++が示す動作は標準に従っていないようです(Leon Timmermanslitbが正しい方向を示してくれたことに感謝します)。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;
}

1
いいえ、これは標準に準拠していませんが、-fpermissiveオプションをオンにするとGCCで機能すると思います。
Leon Timmermans、

コメントは多くの余地を与えていませんが、標準に準拠していない理由についての情報を指摘できますか?(私は議論していない-私は好奇心が強い)
マイケル・バー

Mike B:標準は3.10 p15で述べています:「プログラムが次のタイプのいずれか以外の左辺値を介してオブジェクトの保存された値にアクセスしようとすると、動作は未定義です」そしてそのリストは実際にあなたのケースを含みません行う。
Johannes Schaub-litb 2008年

4
なぜそれが私の別のコメントを追加しないのかわかりません。あなたのtoString呼び出しは修飾されていません。そのため、ベースクラスはテンプレートタイプのパラメーターに依存しているため、ベース内の関数ではなく、常にフリー関数を呼び出します。
Johannes Schaub-litb 2008年

@litb:ポインタをありがとう。ここでは3.10は当てはまらないと思います。doToString()内のtoString()の呼び出しは、「lvalueを介してオブジェクトの格納された値にアクセスする」ことではありません。しかし、あなたの2番目のコメントは正しいです。答えを更新します。
マイケルバー

6

ここで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()を挿入する必要があることに気付きました。


うーん、その投稿のアイデアを使用して独自のバージョンを開発したので、アイデアに他の欠点があることがわかったので、もう一度解答からコードを削除しました。1つは、すべての関数がターゲット型でパブリックでなければならないことです。したがって、これで「f」関数をチェックすることはできません。関数のstruct g { void f(); private: void f(int); };1つがプライベートであるためです(これは、コードがusing g::f;を実行するため、fアクセスできない場合に失敗するためです)。
Johannes Schaub-litb 2009

6

MSVCには__if_existsおよび__if_not_existsキーワード(Doc)があります。Nicolaのtypeof-SFINAEアプローチとともに、OPが探していたように、GCCとMSVCのチェックを作成できました。

更新:ソースはここにあります


6

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");

5

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

これを改善して、テンプレート引数を持つメンバー関数をサポートできます。テンプレート<typename T>をテンプレート<typename T、typename ... Args>に変更すると、マクロ省略記号で「Args ...」を使用して、可変個のテンプレート引数を持つチェック構造体を作成できます。例えば。"void onNext(const T&)"メソッドを検出HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic

4

奇妙なことに誰も私がこのサイトで一度見た次の素晴らしいトリックを提案しませんでした:

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で動作させました。


3

タイプによって「機能」がサポートされているかどうかを確認するために使用できる汎用テンプレート:

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

foo署名と互換性のあるメソッドがあるかどうかを確認するテンプレートdouble(const char*)

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

http://coliru.stacked-crooked.com/a/83c6a631ed42cea4


インラインする方法があるhas_fooのテンプレート呼び出しにはis_supported。私が望むのは、次のようなものですstd::cout << is_supported<magic.foo(), struct1>::value << std::endl;。その理由は、has_foo関数をチェックする前に、チェックしたいさまざまな関数シグネチャごとにを定義したいですか?
CJCombrink 2017

2

このソリューションはどうですか?

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

あいまいであるためtoString、過負荷の場合は失敗し&U::toStringます。
Yakk-Adam Nevraumont 2014年

@ヤク私はキャストがこの問題を解決できると思います。
user1095108 14年

2

ここには多くの答えがありますが、新しい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 ++コンパイラーでライブデモを実行する場合、0の配列サイズが許可されることに注意してください。つまり、使用されたstatic_assertは、失敗してもコンパイラーエラーをトリガーしません。
一般的に使用される回避策は、マクロの「typedef」を「extern」に置き換えることです。


いいえ、しかし私はそれを自分で宣言していて、右辺値を使用していません(コードの上部を見てください)。または、自分自身を納得させて、c ++ 98モードでライブデモを試すこともできます。PS:static_assertもc ++ 98ではありませんが、回避策があります(ライブデモ)
user3296587

おお!それを逃した。:-)
Ian Ni-Lewis

静的アサートが機能しません。0ではなく配列サイズ-1を使用する必要があります(を入れてみてくださいstatic_assert(false);)。派生クラスに特定の機能があるかどうかを判断するためにCRTPに関連してこれを使用していました。機能しないことが判明しましたが、アサートは常に渡されました。髪の毛が抜けました。
ブタは、

私はあなたがg ++を使っていると仮定しています。gcc / g ++には、サイズがゼロの配列(gcc.gnu.org/onlinedocs/gcc/Zero-Length.html)を可能にする拡張機能があることに注意してください
user3296587

演算子をオーバーロードしないように、これを書き直すことはできますか?たとえば、別の演算子を選択しますか?また、has_awesome_member以外のもので名前空間を汚染しないようにしてください。
einpoklum

1

テンプレートメンバー関数を含め、任意のアリティでメンバー関数のすべてのオーバーロードを処理できる私のバージョンは次のとおりです。与えられた引数タイプを使用して、あるクラスタイプへのメンバー関数呼び出しを行う場合、(1)有効、(2)あいまい、または(3)実行不可能という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


1

C ++ 14のすべてのメタプログラミングをスキップしfit::conditionalて、Fitライブラリから次のように書くだけです。

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()
);

1
fit標準以外のライブラリやライブラリに依存する必要がないようにこれを書くのはどれほど簡単ですか?
アインポクルム

1

C ++ 20では、次のように記述できます。

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

動作するコードの例を次に示します。

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で呼び出されたときintに取る関数よりも優先される追加の引数を取る関数を有効にします。long0

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
}

0

私は同様の問題を抱えていました:

いくつかの基本クラスから派生する可能性のあるテンプレートクラス、特定のメンバーを持つもの、持たないもの。

"typeof"(Nicola Bonelli's)の回答と同様に解決しましたが、MSCLで正しくコンパイルおよび実行されるようにdecltypeを使用しています。

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

0

C ++ 17でそれを行うもう1つの方法(boost:hanaに触発された)。

一度書いて何度も使う。has_something<T>型特性クラスは必要ありません。

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

-1
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!";
}

6
「回答の説明は不要です」...回答を改善するために、回答に有益な説明を追加してください。ありがとう。
YesThatIsMyName
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.