14

科学計算のための C++ に関する優れた論文があり、著者 (T. Veldhuizen) は型昇格に対処するための特性ベースのアプローチを提案しています。私はそのようなアプローチを使用しており、効果的であることがわかりました。

#include<iostream>
#include<complex>
#include<typeinfo>

template<typename T1, typename T2>
struct promote_trait{};

#define DECLARE_PROMOTION(A, B, C) template<> struct promote_trait<A, B> { using T_promote = C;};

DECLARE_PROMOTION(int, char, int);
DECLARE_PROMOTION(int, float, float);
DECLARE_PROMOTION(float, std::complex<float>, std::complex<float>);

// similarly for all possible type combinations...

template<typename T1, typename T2>
void product(T1 a, T2 b) {
  using T = typename promote_trait<T1, T2>::T_promote;
  T ans = T(a) * T(b);  
  std::cout<<"received "
           <<typeid(T1).name()<<"("<<a<<")"<<" * "
           <<typeid(T2).name()<<"("<<b<<")"<<" ==> "
           <<"returning "
           <<typeid(T).name()<<"("<<ans<<")"<<std::endl;
}

int main() {
  product(1, 'a');
  product(1, 2.0f);
  product(1.0f, std::complex<float>(1.0f, 2.0f));
  return 0;
}

出力:

received i(1) * c(a) ==> returning i(97)
received i(1) * f(2) ==> returning f(2)
received f(1) * St7complexIfE((1,2)) ==> returning St7complexIfE((1,2))

typeinfo によって返される型の名前は実装に依存します。あなたの出力は、OS X 10.7.4 で GCC 4.7.2 を使用した私のものとは異なる場合があります。

本質的に、このアプローチは、promote_trait単に 1 つの型定義を含む を定義します。つまり、特定の方法で動作するときに 2 つの型をプロモートする必要がある型です。考えられるすべてのプロモーションを宣言する必要があります。

1 つの関数が両方の型を受け取った場合、関数は に依存してpromote_trait、昇格された正しい結果の型を推測します。特定のペアに対して特性が定義されていない場合、コードはコンパイルに失敗します (望ましい機能です)。

さて、問題の論文は 2000 年に書かれたものであり、C++ が過去 10 年間で劇的に進化したことがわかっています。私の質問は次のとおりです。

Veldhuizen によって導入された特性ベースのアプローチと同じくらい効果的な型昇格に対処するための、最新の慣用的な C++ 11 アプローチはありますか?

編集 (使用時std::common_type)

Luc Danton の提案に基づいて、 を使用する次のコードを作成しましたstd::common_type

#include<iostream>
#include<complex>
#include<typeinfo>
#include<typeindex>
#include<string>
#include<utility>
#include<map>

// a map to homogenize the type names across platforms
std::map<std::type_index, std::string> type_names = {
  {typeid(char)                 , "char"},  
  {typeid(int)                  , "int"},
  {typeid(float)                , "float"},
  {typeid(double)               , "double"},
  {typeid(std::complex<int>)    , "complex<int>"},
  {typeid(std::complex<float>)  , "complex<float>"},
  {typeid(std::complex<double>) , "complex<double>"},
};

template<typename T1, typename T2>
void promotion(T1 a, T2 b) {
  std::string T1name = type_names[typeid(T1)];
  std::string T2name = type_names[typeid(T2)];
  std::string TPname = type_names[typeid(typename std::common_type<T1, T2>::type)];  
  std::cout<<T1name<<"("<<a<<") and "<<T2name<<"("<<b<<") promoted to "<<TPname<<std::endl;
}

int main() {
  promotion(1, 'a');
  promotion(1, 1.0);
  promotion(1.0, 1);
  promotion(std::complex<double>(1), 1);
  promotion(1.0f, 1);
  promotion(1.0f, 1.0);
  promotion(std::complex<int>(1), std::complex<double>(1));
  promotion(std::complex<double>(1), std::complex<int>(1));
  promotion(std::complex<float>(0, 2.0f), std::complex<int>(1));

  return 0;
}

出力付き:

int(1) and char(a) promoted to int
int(1) and double(1) promoted to double
double(1) and int(1) promoted to double
complex<double>((1,0)) and int(1) promoted to complex<double>
float(1) and int(1) promoted to float
float(1) and double(1) promoted to double
complex<int>((1,0)) and complex<double>((1,0)) promoted to complex<int>
complex<double>((1,0)) and complex<int>((1,0)) promoted to complex<int>
complex<float>((0,2)) and complex<int>((1,0)) promoted to complex<int>

最後の 3 つのプロモーションを除くすべてが、私が期待していたものであることに気付いて驚いています。なぜcomplex<int>and complex<double>orcomplex<float>が ! に昇進するのcomplex<int>でしょうか?

4

2 に答える 2

5

catcradle answer のように、decltypeおよびcommon_type(およびそのカスタム特殊化) は、おそらく Veldhuizen が念頭に置いている変換特性の必要性に対する C++11 の優れた代替品です。ただし、 2 つの型を 1 つにマップする関数 (二項演算子) の評価を非常に具体的にする必要がある場合は、まだ不十分です。(つまりdecltype、問題の数学的ドメインについてはわかりません)。

私の見解では、Boost.MPL マップhttp://www.boost.org/doc/libs/1_53_0/libs/mpl/doc/refmanual/map.htmlに頼ることができます。これには C++11 も必要ありません。 、その時点で MPL が作成されていなかっただけです。

#include<iostream>
#include<complex>
#include<typeinfo>
#include <boost/mpl/map.hpp>
#include <boost/mpl/at.hpp>
// all traits in one place, no need for MACROS or C++11, compile error if the case does not exist.
using namespace boost::mpl;
typedef map<
    pair<pair<int, char>, int>,
    pair<pair<int, float>, int>,
    pair<pair<float, std::complex<float> >, std::complex<float> >
> mapped_promotion;

template<typename T1, typename T2>
void product(T1 a, T2 b) {
  typedef typename at<mapped_promotion, pair<T1, T2> >::type T;

  T ans = T(a) * T(b);  
  std::cout<<"received "
           <<typeid(T1).name()<<"("<<a<<")"<<" * "
           <<typeid(T2).name()<<"("<<b<<")"<<" ==> "
           <<"returning "
           <<typeid(T).name()<<"("<<ans<<")"<<std::endl;
}

int main() {
  product(1, 'a');
  product(1, 2.0f);
  product(1.0f, std::complex<float>(1.0f, 2.0f));
  return 0;
}

MPL を使用するもう 1 つの利点はBoost.Fusion、型の「代数」を扱い始めると、後で簡単に移行できることです。また、C++11 コア言語の Boost.Fusion の機能を置き換えるものは何もありません。


以下は、より一般的な解決策です。上記でアプリケーションに十分である場合は、読むのをやめることができます。これは、MPL を組み合わせ、decltypeC++11 を必要とします。これにより、未指定の型のペアが decltype ソリューションにデフォルト設定されます。トリックは、の戻り値がmpl::mapメタタイプvoid_(ペアが見つからない) であるかどうかを確認することです。

...
#include <type_traits> 
//specific promotions
using namespace boost::mpl;
typedef map<
    pair<pair<int, char>, int>,
    pair<pair<int, float>, int>,
    pair<pair<float, std::complex<float> >, std::complex<float> >
> specific_mapped_promotion;

//promotion for unspecified combinations defaults to decltype type deduction.
template<class P1, class P2>
struct loose_mapped_promotion : std::conditional<
    std::is_same<typename at<specific_mapped_promotion, pair<P1, P2> >::type, mpl_::void_>::value,
    decltype( std::declval<P1>()*std::declval<P2>() ),
    typename at<specific_mapped_promotion, pair<P1, P2> >::type
> {};
template<typename T1, typename T2>
void product(T1 a, T2 b) {
  typedef typename loose_mapped_promotion<T1, T2>::type T;

  T ans = T(a) * T(b);
  ...
}
int main() {
   product(1.0, std::complex<double>(1.0f, 2.0f)); // now accepted, although no explicit trait was made
}

std::common_type最後の注意:使用したい場合は、特別な場合にオーバーロードしても問題ないようです: http://www.cplusplus.com/reference/type_traits/common_type/

于 2013-05-31T20:58:05.593 に答える
3

これに使用できると思いますdecltype

template <typename T, typename U>
void product(T t, U u) 
{
    std::cout << typeid(decltype(t * u)).name() << std::endl;
}

またはdeclval

#include <utility>
template <typename T, typename U>
void product() 
{
    std::cout << typeid(decltype(std::declval<T>() * std::declval<U>())).name() << std::endl;
}

編集あなたはT ans = T(a) * T(b);ただ使うことができますautoauto ans = T(a) * T(b);

于 2013-05-31T20:29:30.897 に答える