Учитывая, что никто не упомянул об этом ...
Некоторые языки высокого уровня, такие как Python и Java, поставляются с инструментами для преодоления ограничений двоичной с плавающей запятой. Например:
decimal
Python [и g0] и класс BigDecimal
Java [], которые представляют числа внутри с десятичной нотацией (в отличие от двоичная запись). Оба имеют ограниченную точность, поэтому они все еще подвержены ошибкам, однако они решают наиболее распространенные проблемы с бинарной арифметикой с плавающей запятой. Десятичные числа очень хороши при работе с деньгами: десять центов плюс двадцать центов всегда ровно тридцать центов: >>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
Модуль decimal
Python основан на стандарте IEEE стандарта 802.118 . fractions
модуль и класс Apache Common BigFraction
. Оба представляют собой рациональные числа как пары (numerator, denominator)
, и они могут давать более точные результаты, чем десятичная арифметика с плавающей запятой. Ни одно из этих решений не идеально (особенно, если мы смотрим на выступления, или если мы требуют очень высокой точности), но все же они решают большое количество проблем с двоичной арифметикой с плавающей запятой.
Я не уверен, правильно ли я вас понимаю, но вы можете использовать SFINAE для обнаружения присутствия функции во время компиляции. Пример из моего кода (тесты, если класс имеет функцию-член size_t used_memory () const).
template<typename T>
struct HasUsedMemoryMethod
{
template<typename U, size_t (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
template<typename U> static int Test(...);
static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};
template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
// We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
ReportMemUsage(m,
std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}
Вот некоторые фрагменты использования: * Кишки для всего этого дальше
Проверьте член 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;
ИЛИ
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)
Хорошо. Вторая попытка. Это нормально, если вам это не нравится, я ищу больше идей.
Статья Херба Саттера рассказывает о чертах. Таким образом, у вас может быть класс признаков, у экземпляра по умолчанию есть резервное поведение, а для каждого класса, где существует ваша функция-член, класс признаков специализирован для вызова функции-члена. Я полагаю, что в статье Херба упоминается техника для этого, так что она не требует большого количества копирования и вставки.
Как я уже сказал, возможно, вам не нужна дополнительная работа с «тегами», классы, которые реализуют этот элемент. В этом случае я смотрю на третье решение ....
Принятый ответ на этот вопрос интроспекции член-функции compiletime, хотя он по праву популярен, имеет зацепку, которая может наблюдаться в следующей программе:
#include <type_traits>
#include <iostream>
#include <memory>
/* Here we apply the accepted answer's technique to probe for the
the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
template<typename U, E (U::*)() const> struct SFINAE {};
template<typename U> static char Test(SFINAE<U, &U::operator*>*);
template<typename U> static int Test(...);
static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};
using namespace std;
/* Here we test the `std::` smart pointer templates, including the
deprecated `auto_ptr<T>`, to determine in each case whether
T = (the template instantiated for `int`) provides
`int & T::operator*() const` - which all of them in fact do.
*/
int main(void)
{
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
return 0;
}
Построено с GCC 4.6.3 , программные выходы 110
- информируя нас о том, что T = std::shared_ptr<int>
предоставляет not , предоставить int & T::operator*() const
.
Если вы еще не разумны в этом вопросе, то посмотрите на определение std::shared_ptr<T>
в заголовке <memory>
пролить свет. В этой реализации std::shared_ptr<T>
происходит из базового класса, из которого он наследует operator*() const
. Таким образом, экземпляр шаблона SFINAE<U, &U::operator*>
, который представляет собой «поиск» оператора для U = std::shared_ptr<T>
, не будет выполняться, поскольку std::shared_ptr<T>
не имеет operator*()
в своем собственном праве, а создание экземпляра шаблона не «наследует».
Эта защелка не влияет на хорошо известный подход SFINAE, используя «Theofofofof () Trick», для обнаружения только того, имеет ли T
некоторая функция-член mf
(см., например, этот ответ и Комментарии). Но установление того, что T::mf
существует, часто (обычно?) Не достаточно хорошо: вам также может потребоваться установить, что у него есть желаемая подпись. Вот где проиллюстрирована техническая оценка. Указанный вариант желаемой сигнатуры вписывается в параметр типа шаблона, который должен удовлетворять &T::mf
для успешного использования зонда SFINAE. Но этот метод экземпляра шаблона дает неправильный ответ, когда T::mf
унаследован.
Безопасный метод SFINAE для интроспекции компиляции T::mf
должен избегать использования &T::mf
в аргументе шаблона для создания экземпляра типа от которого зависит разрешение шаблона функции SFINAE. Вместо этого разрешение функции шаблона SFINAE может зависеть только от точно соответствующих деклараций типа, используемых в качестве типов аргументов перегруженной функции зондирования SFINAE.
. В ответ на вопрос, который соблюдает это ограничение, я проиллюстрирую обнаружение компиляции E T::operator*() const
для произвольных T
и E
. Тот же шаблон применит mutatis mutandis для проверки любой другой сигнатуры метода участника.
#include <type_traits>
/*! The template `has_const_reference_op<T,E>` exports a
boolean constant `value that is true iff `T` provides
`E T::operator*() const`
*/
template< typename T, typename E>
struct has_const_reference_op
{
/* SFINAE operator-has-correct-sig :) */
template<typename A>
static std::true_type test(E (A::*)() const) {
return std::true_type();
}
/* SFINAE operator-exists :) */
template <typename A>
static decltype(test(&A::operator*))
test(decltype(&A::operator*),void *) {
/* Operator exists. What about sig? */
typedef decltype(test(&A::operator*)) return_type;
return return_type();
}
/* SFINAE game over :( */
template<typename A>
static std::false_type test(...) {
return std::false_type();
}
/* This will be either `std::true_type` or `std::false_type` */
typedef decltype(test<T>(0,0)) type;
static const bool value = type::value; /* Which is it? */
};
В этом решении перегруженная функция зонда SFINAE test()
«вызывается рекурсивно», , (Конечно, он вообще не вызывается вообще, он просто имеет возвращаемые типы гипотетических вызовов, разрешенных компилятором.)
Нам нужно исследовать хотя бы одну и не более двух точек информации:
T::operator*()
? Если нет, мы закончили. T::operator*()
, является ли его подпись E T::operator*() const
? Мы получаем ответы, оценивая возвращаемый тип один вызов test(0,0)
. Это делается с помощью:
typedef decltype(test<T>(0,0)) type;
Этот вызов может быть разрешен в /* SFINAE operator-exists :) */
перегрузке test()
, или он может решить перегрузку /* SFINAE game over :( */
. Он не может решить перегрузку /* SFINAE operator-has-correct-sig :) */
, потому что тот ожидает только один аргумент, и мы проходим два.
Почему мы проходим два? Просто заставить разрешение исключить /* SFINAE operator-has-correct-sig :) */
. Второй аргумент не имеет никакой другой значимости.
Этот вызов test(0,0)
разрешит /* SFINAE operator-exists :) */
, если первый аргумент 0 удовлетворяет первому типу параметра этой перегрузки, который является decltype(&A::operator*)
, с A = T
. 0 будет удовлетворять этому типу на всякий случай T::operator*
.
Предположим, что компилятор говорит «Да». Затем это происходит с /* SFINAE operator-exists :) */
, и ему нужно определить тип возврата вызова функции, который в этом случае decltype(test(&A::operator*))
- тип возврата еще одного вызова test()
.
На этот раз , мы передаем только один аргумент, &A::operator*
, который мы теперь знаем, или мы не были бы здесь. Вызов test(&A::operator*)
может разрешить либо /* SFINAE operator-has-correct-sig :) */
, либо снова, чтобы разрешить /* SFINAE game over :( */
. Вызов будет соответствовать /* SFINAE operator-has-correct-sig :) */
на всякий случай, если &A::operator*
удовлетворяет типу одного параметра этой перегрузки, который E (A::*)() const
, с A = T
.
Компилятор скажет «Да», если T::operator*
имеет эту желаемую подпись, а затем снова должен оценивать возвращаемый тип перегрузки. Больше нет «рекурсий»: это std::true_type
.
Если компилятор не выбирает /* SFINAE operator-exists :) */
для вызова test(0,0)
или не выбирает /* SFINAE operator-has-correct-sig :) */
для вызова test(&A::operator*)
, тогда в любом случае он идет с /* SFINAE game over :( */
, а конечным типом возврата является std::false_type
.
Вот тестовая программа, в которой показан шаблон, который дает ожидаемые ответы в разнообразной выборке случаев (GCC 4.6.3 снова ).
// To test
struct empty{};
// To test
struct int_ref
{
int & operator*() const {
return *_pint;
}
int & foo() const {
return *_pint;
}
int * _pint;
};
// To test
struct sub_int_ref : int_ref{};
// To test
template<typename E>
struct ee_ref
{
E & operator*() {
return *_pe;
}
E & foo() const {
return *_pe;
}
E * _pe;
};
// To test
struct sub_ee_ref : ee_ref<char>{};
using namespace std;
#include <iostream>
#include <memory>
#include <vector>
int main(void)
{
cout << "Expect Yes" << endl;
cout << has_const_reference_op<auto_ptr<int>,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,int &>::value;
cout << has_const_reference_op<shared_ptr<int>,int &>::value;
cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
cout << has_const_reference_op<std::vector<int>::const_iterator,
int const &>::value;
cout << has_const_reference_op<int_ref,int &>::value;
cout << has_const_reference_op<sub_int_ref,int &>::value << endl;
cout << "Expect No" << endl;
cout << has_const_reference_op<int *,int &>::value;
cout << has_const_reference_op<unique_ptr<int>,char &>::value;
cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
cout << has_const_reference_op<unique_ptr<int>,int>::value;
cout << has_const_reference_op<unique_ptr<long>,int &>::value;
cout << has_const_reference_op<int,int>::value;
cout << has_const_reference_op<std::vector<int>,int &>::value;
cout << has_const_reference_op<ee_ref<int>,int &>::value;
cout << has_const_reference_op<sub_ee_ref,int &>::value;
cout << has_const_reference_op<empty,int &>::value << endl;
return 0;
}
Есть ли новые недостатки в этой идее? Может ли он стать более общим без лишних падений, чтобы избежать этого?
Этого должно быть достаточно, если вы знаете имя функции-члена, которую вы ожидаете. (В этом случае функция bla не может создать экземпляр, если нет какой-либо функции-члена (писать все, что работает в любом случае, является жестким, потому что отсутствует частичная специализация функции. Возможно, вам придется использовать шаблоны классов). Кроме того, разрешающая структура (которая аналогичен enable_if) также может быть запрограммирован на тип функции, которую вы хотите иметь в качестве члена.
template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
A a;
B b;
bla(b);
bla(a);
}
Без поддержки C ++ 11 (decltype
) это может работать:
#include <iostream>
using namespace std;
struct A { void foo(void); };
struct Aa: public A { };
struct B { };
struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };
template<typename T>
struct FooFinder {
typedef char true_type[1];
typedef char false_type[2];
template<int>
struct TypeSink;
template<class U>
static true_type &match(U);
template<class U>
static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);
template<class U>
static false_type &test(...);
enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};
int main() {
cout << FooFinder<A>::value << endl;
cout << FooFinder<Aa>::value << endl;
cout << FooFinder<B>::value << endl;
cout << FooFinder<retA>::value << endl;
cout << FooFinder<argA>::value << endl;
cout << FooFinder<constA>::value << endl;
cout << FooFinder<varA>::value << endl;
}
A
, Aa
и B
являются рассматриваемыми кланами, Aa
является специальным, который наследует элемент, который мы ищем.
В FooFinder
true_type
и false_type
] - это замены для соответствующих классов C ++ 11. Также для понимания мета-программирования шаблонов они раскрывают саму основу трюка SFINAE-sizeof.
TypeSink
- это структура шаблона, которая используется позже, чтобы потопить интегральный результат sizeof
в экземпляр шаблона для формирования типа.
Функция match
- это еще один шаблон типа SFINAE, который остается без общего экземпляра. Поэтому он может быть создан только в том случае, если тип его аргумента совпадает с типом, для которого он был специализирован.
Обе функции test
вместе с объявлением перечисления окончательно образуют центральный шаблон SFINAE. Существует универсальный метод, использующий эллипсис, который возвращает false_type
и аналог с более конкретными аргументами.
Чтобы иметь возможность создать экземпляр функции test
с аргументом шаблона T
, функция match
должна быть инстанцирована, так как ее тип возврата требуется для создания экземпляра аргумента TypeSink
. Предостережение заключается в том, что &U::foo
, будучи завернутым в аргумент функции, является not , на который ссылается внутри специализации аргумента шаблона, поэтому унаследованный поиск элементов все еще имеет место.
Для этого нам нужно будет использовать:
type_traits
мы хотим вернуть true_type
или false_type
из наших перегрузок true_type
, ожидающую перегрузки int
и false_type
, ожидающие использования Variadic Parameters: «Самый низкий приоритет преобразования многоточия при разрешении перегрузки» true_type
мы будем использовать declval
и decltype
, позволяющие нам обнаружить функцию, не зависящую от различий или перегрузок типа возврата между методами Вы можете увидеть живой пример этого здесь , но я объясню это ниже:
Я хочу проверить существование функции с именем test
, которая принимает тип, конвертируемый из int
, тогда мне нужно было бы объявить эти две функции:
template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
decltype(hasTest<a>(0))::value
is true
(Примечание: нет необходимости создавать специальные функции для работы с перегрузкой void a::test()
, принимается void a::test(int)
) decltype(hasTest<b>(0))::value
есть true
(Поскольку int
можно преобразовать в double
, принимается int b::test(double)
независимо от типа возврата) decltype(hasTest<c>(0))::value
is false
(c
не имеет метода с именем test
который принимает тип, конвертируемый из int
, поэтому это не принимается) Это решение имеет 2 недостатка:
test()
? Поэтому важно, чтобы эти функции были объявлены в пространстве имен деталей, или, в идеале, если они должны использоваться только с классом, они должны быть объявлены частным образом этим классом. С этой целью я написал макрос, который поможет вам абстрагировать эту информацию:
#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
template <typename T> static false_type __ ## DEFINE(...); \
template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));
Вы можете использовать это как:
namespace details {
FOO(test(declval<int>()), test_int)
FOO(test(), test_void)
}
Затем вызывать details::test_int<a>::value
или details::test_void<a>::value
даст true
или false
для встроенного кода или метапрограммирования.
Я считаю, что ответ, который вы ищете, здесь.
http://www.martinecker.com/wiki/index.php?title=Detecting_the_Existence_of_Operators_at_Compile-Time
и немного более заполненный пример здесь
Я использую эту технику для обнаружения наличие поддерживающего операционного оператора & lt; на класс, о котором идет речь, а затем генерировать другой бит кода в зависимости.
Я не думал, что это возможно, прежде чем найти связанное решение, но это очень аккуратный трюк. Проведите время, понимая код, и это очень стоит.
Brad
Вот более простой ответ на ответ Майка Кингхана. Это обнаружит унаследованные методы. Он также проверит наличие точной подписи (в отличие от подхода jrok, который позволяет преобразовывать аргументы).
template <class C>
class HasGreetMethod
{
template <class T>
static std::true_type testSignature(void (T::*)(const char*) const);
template <class T>
static decltype(testSignature(&T::greet)) test(std::nullptr_t);
template <class T>
static std::false_type test(...);
public:
using type = decltype(test<C>(nullptr));
static const bool value = type::value;
};
struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");
Вы можете использовать std :: is_member_function_pointer
class A {
public:
void foo() {};
}
bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;