Идиоматический продвижение по типу C++ 11
Существует отличная статья на С++ для научных вычислений, где автор (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, зависит от реализации; ваш результат может отличаться от моего, который использовал GCC 4.7.2 на OS X 10.7.4
В сущности, подход определяет promote_trait
, который содержит просто одно определение типа: тип, которому должны поощряться два типа при работе определенным образом. Нужно объявить все возможные рекламные акции.
Когда одна функция получает оба типа, она полагается на promote_trait
для вывода правильного, продвинутого типа результата. Если черта не определена для данной пары, код не скомпилируется (желательная функция).
Теперь эта статья была написана в 2000 году, и мы знаем, что С++ значительно изменилась за последнее десятилетие. Поэтому мой вопрос следующий:
Есть ли современный, идиоматический подход С++ 11 к решению проблемы продвижения по типу, столь же эффективный, как подход, основанный на характеристиках, введенный Veldhuizen?
Изменить (при использовании std::common_type
)
Основываясь на предположении Люка Дантона, я создал следующий код, который использует 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>
Я удивлен, заметив, что все, кроме последних трех рекламных акций, я ожидал. Зачем complex<int>
и complex<double>
или complex<float>
продвигаться до complex<int>
!?
Ответы
Ответ 1
Как и в ответе catscradle, decltype
и common_type
(и специализированные специализации для него), вероятно, являются хорошей заменой С++ 11 для потребности в характеристиках преобразования, которые имеет в виду Veldhuizen. Тем не менее, он все равно будет падать, если вам нужно быть очень специфичным для оценки функции, которая отображает два типа в один (двоичный оператор). (Другими словами, decltype
не знает о математической области вашей проблемы).
Я считаю, что вы можете прибегнуть к картам Boost.MPL http://www.boost.org/doc/libs/1_53_0/libs/mpl/doc/refmanual/map.html, это даже не требует С++ 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 заключается в том, что вы можете легко перейти к Boost.Fusion
позже, что может случиться, когда вы начнете общаться с "алгебрами" типов. И нет ничего, что могло бы заменить функциональность Boost.Fusion на основном языке С++ 11.
Следующее - более общее решение, вы можете прекратить чтение, если для вашего приложения было достаточно выше, что сочетает в себе MPL и decltype
и требует С++ 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/
Ответ 2
Я думаю, вы можете использовать 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;
}
EDIT Для T ans = T(a) * T(b);
вы можете просто использовать auto
, auto ans = T(a) * T(b);