4

サードパーティのSDKは、いくつかのtypedefを定義しています。

typedef unsigned char SDK_BYTE
typedef double SDK_DOUBLE
typedef unsigned char SDK_BOOLEAN

また、バリアント型SdkVariantを定義します。

class SdkVariant
{
public:
    enum SdkType { SdkByte, SdkDouble, SdkBoolean };
    bool toByte(SDK_BYTE&);
    bool toDouble(SDK_DOUBLE&);
    bool toBool(SDK_BOOLEAN&);
    SdkType type();
};

このようなバリアントから値を取得することは、次のようになります(含まれている値のタイプがわかっていると仮定します)。

SdkVariant variant(foobar());
double value;
bool res = variant.toDouble(value);
if (!res)
    diePainfully();
else
    doSomethingWith(value);

これは非常に冗長であるため、値の取得とエラー処理を実行できるvariant_cast-function-classを提供したいと思います。

// general interface:
template<class T>
class variant_cast
{
public:
    T operator()(const SdkVariant& variant);
};

// template specializations:
template<>
SDK_DOUBLE variant_cast<SDK_DOUBLE>::operator()(const SdkVariant& variant)
{
    SDK_DOUBLE value;
    bool res = variant.toDouble(value);
    if (!res)
        diePainfully();
    return value;
}

template<>
SDK_BYTE variant_cast<SDK_BYTE>::operator()(const SdkVariant& variant)
{
    SDK_BYTE value;
    bool res = variant.toByte(value);
    if (!res)
        diePainfully();
    return value;
}

template<>
SDK_BOOLEAN variant_cast<SDK_BOOLEAN>::operator()(const SdkVariant& variant)
{
    SDK_BOOLEAN value;
    bool res = variant.toByte(value);
    if (!res)
        diePainfully();
    return value;
}

SDK_BYTEとSDK_BOOLEANは同じタイプ(unsigned char)であるため、これはコンパイルされません(C2995:関数テンプレートは既に定義されています)。私の考えは、プリプロセッサにSDK_BYTEとSDK_BOOLEANが同じであることを確認させ、同じである場合は、両方に単一のテンプレート特殊化を定義することです。それらが異なる場合は、上記の2つの別個の専門分野を使用する必要があります。このような:

#if SDK_BYTE == SDK_BOOLEAN
template<>
SDK_BYTE variant_cast<SDK_BYTE>::operator()(const SdkVariant& variant)
{
    SDK_BYTE value;
    bool res;
    if (variant.type() == SdkByte)
        res = variant.toByte(value);
    else
        res = variant.toBool(value);
    if (!res)
        diePainfully();
    return value;
}
#else
    // code from above
#endif

上記のコードの問題は、プリプロセッサが2つのtypedefを解決できないように見えることです。前処理中に2つのtypedefを(正しく)比較する方法はありますか?そうでない場合、コンパイラがtypedefを解決するのを妨げて、SDK_BYTEとSDK_BOOLEANの2つの異なるテンプレート特殊化を受け入れる方法はありますか?そうでない場合でも、単一のテンプレートの特殊化を提供し、BOOST_STATIC_ASSERTを使用して、SDK_BYTEとSDK_BOOLEANが等しくない場合にコンパイラーを失敗させることができますが、問題を解決するためのより良い方法はありますか?

4

4 に答える 4

7

std::enable_ifC++11 がオプションの場合は、 と を使用して考えられる解決策を示すコードを次に示しますstd::is_same

#include <iostream>
#include <type_traits>

struct SdkVariant
{
};

typedef int   type1;
typedef float type2;

template <typename T, typename Enable=void>
class variant_cast
{
public:
  /* Default implementation of the converter. This is undefined, but
     you can define it to throw an exception instead. */
  T operator()(const SdkVariant &v);
};

/* Conversion for type1. */
template <typename T>
class variant_cast<T,typename std::enable_if<std::is_same<T,type1>::value>::type>
{
public:
  type1 operator()(const SdkVariant &v)
  {
    return type1 { 0 };
  }
};

/* Conversion for type2, IF type2 != type1. Otherwise this
   specialization will never be used. */
template <typename T>
class variant_cast<T,typename std::enable_if<
         std::is_same<T,type2>::value
      && !std::is_same<type1,type2>::value>::type>
{
 public:
  type2 operator()(const SdkVariant &v)
  {
    return type2 { 1 };
  }
};

int main()
{
  variant_cast<type1> vc1;
  variant_cast<type2> vc2;
  std::cout << vc1({}) << std::endl;
  std::cout << vc2({}) << std::endl;
  return 0;
}

いくつかのメモ:

  1. そのライブラリによって定義されるさまざまなタイプの代わりに、私は定義したものtype1type2
  2. 空のSdkVariant構造体をダミーとして定義しました
  3. そのダミーは空であるため、私の変換は実際には何も変換しません。に変換する場合は定数 (値 0) を出力し、 に変換する場合type1は定数 (値 1) を出力するだけです (が実際に と異なるtype2場合)。type2type1
  4. 必要なことを行うかどうかをテストするには、の定義を次のように置き換えることができtype2ます

    typedef int type2;
    

    の定義と同じですtype1。それでもコンパイルされ、二重定義に関連するエラーは発生しません。

  5. GCC 4.7.0 と--std=c++11オプションを使用してこれをテストしました。

std::enable_ifテンプレートの特殊化の部分的な使用と明示的な使用に関する注意事項

のコンバーターtype1は次のように宣言されます。

template <typename T>
variant_cast<T,typename std::enable_if<std::is_same<T,type1>::value>::type>

つまり、 と同じ任意の型Ttype1に対して定義されます。代わりに、明示的な特殊化を使用することもできました

template <>
variant_cast<type1>

これははるかに簡単で、機能します

私がそれをしなかった唯一の理由はtype2それtype2が と同じかどうかを確認する必要があるためtype1、つまり を使用する必要があるためstd::enable_ifです。

template <>
class variant_cast<type2,
   typename std::enable_if<!std::is_same<type1,type2>::value>::type>

std::enable_if残念ながら、明示的な特殊化はテンプレートではなく、実際のデータ型であり、コンパイラが処理する必要があるため、明示的な特殊化では使用できません。type1type2が同一の場合、次のようになります。

typename std::enable_if<!std::is_same<type1,type2>::value>::type

方法が機能するため、存在しませんstd::enable_if。したがって、このデータ型をインスタンス化できないため、コンパイルは失敗します。

と同じTtype2のコンバーターを定義することにより、 の明示的なインスタンス化を回避するためtype2、コンパイラーに処理を強制しません。が実際に存在するtype2場合にのみ、テンプレートの特殊化を処理します。std::enable_if<...>::typeそれ以外の場合は、単純に無視されます。これは、まさに私たちが望んでいることです。

繰り返しますが、type1(およびそれtype3以降のtype4など) の場合は、明示的なインスタンス化が機能します。

何らかの型と同じ型に対してTtypeテンプレートの特殊化を定義することは、正式な理由で明示的な特殊化を使用できない場合に一般的に適用できるトリックであることを指摘する価値があると思います。そのため、部分的な特殊化を使用しますが、本当にこの1つのタイプのみにバインドしたい. たとえば、メンバー テンプレートは、それを囲むテンプレートも明示的にインスタンス化されない限り、明示的にインスタンス化できません。と の組み合わせを使用するとstd::enable_ifstd::is_sameおそらくそこでも役立ちます。

于 2012-08-16T09:30:33.430 に答える
1

あなたはこのようにそれを行うことができます:

SDK_BYTE asByte(SdkVariant & var)
{
  SDK_BYTE byte;
  bool const ok = var.toByte(byte);
  if (!ok) diePainfully();
  return byte;
}

SDK_DOUBLE asDouble(SdkVariant & var)
{
  SDK_DOUBLE d;
  bool const ok = var.toDouble(d);
  if (!ok) diePainfully();
  return d;
}

SDK_BOOLEAN asBoolean(SdkVariant & var)
{
  SDK_BOOLEAN b;
  bool const ok = var.toBool(b);
  if (!ok) diePainfully();
  return b;
}

static const bool byteAndBooleanAreTheSame = std::is_same<SDK_BYTE, SDK_BOOLEAN>::value;

template <bool b>
struct VariantCastImpl
{
  template <typename T> T cast(SdkVariant & var) const;

  template <> SDK_DOUBLE cast(SdkVariant & var) const { return asDouble(var); }
  template <> SDK_BYTE cast(SdkVariant & var) const { return asByte(var); }
  template <> SDK_BOOLEAN cast(SdkVariant & var) const { return asBoolean(var); }
};

template <>
struct VariantCastImpl<false>
{
  template <typename T> T cast(SdkVariant & var) const;

  template <> SDK_DOUBLE cast(SdkVariant & var) const { return asDouble(var); }
  template <> SDK_BYTE cast(SdkVariant & var) const
  {
    if (var.type() == SdkVariant::SdkByte)
    {
      return asByte(var);
    }
    else if (var.type() == SdkVariant::SdkBoolean)
    {
      return asBoolean(var);
    }
    else
    {
      diePainfully();
      return SDK_BYTE(); // dummy return, I assume diePainfully throws something
    }
  }
};

template <typename T>
T variant_cast(SdkVariant & var)
{
  return VariantCastImpl<!byteAndBooleanAreTheSame>().cast<T>(var);
};
于 2012-08-16T10:31:07.737 に答える
0

BOOST_STRONG_TYPEDEF を使用すると、コードは次のようになります。

BOOST_STRONG_TYPEDEF(SDK_BYTE, mySDK_BYTE)
BOOST_STRONG_TYPEDEF(SDK_BOOLEAN, mySDK_BOOLEAN)

template<>
mySDK_BYTE variant_cast<mySDK_BYTE>::operator()(const SdkVariant& variant)
{
    SDK_BYTE value;
    bool res = variant.toByte(value);
    if (!res)
        diePainfully();
    return value;
}

template<>
mySDK_BOOLEAN variant_cast<mySDK_BOOLEAN>::operator()(const SdkVariant& variant)
{
    SDK_BOOLEAN value;
    bool res = variant.toByte(value);
    if (!res)
        diePainfully();
    return value;
}

これは実際に機能します。唯一の欠点は、SDK_BOOLEANorを取得するためにorSDK_BYTEを書かなければならないことです。ありがとうゼオ。variant_cast<mySDK_BOOLEAN>(myVariant)variant_cast<mySDK_BYTE>(myVariant)

于 2012-08-16T11:14:33.610 に答える