Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду.
Представьте, что Вы написали шаблонную функцию
template < class T >
std::string
f(T x)
{
return"generic";
}
Она замечательно работает, то Вам захотелось добавить специализацию для арифметических типов, т.е. чтобы для типов char, int, double вызывалась специальная функция
template < class T >
std::string
f(T x)
{
return"arithmetic";
}
а для всех остальных — самая общая функция.
Вопрос — как этого добиться?
В Boost есть замечательная штука — enable_if, которая возволяет "включить" определенную перегрузку функции, если удовлетворено некоторое условие.
В нашем случае это будет выглядеть так:
template < class T >
typename boost::enable_if< boost::is_arithmetic< T >, std::string >::type
f(T x)
{
return"arithmetic";
}
теперь эта специализация не будет рассматриваться для разрешения перегрузки, если тип аргумента не арифметический.
Однако если он арифметический, то сработают обе функции и мы получим неоднозначность в точке вызова.
Чтобы избавиться от неоднозначности, мы теперь должны "выключить" нашу самую обобщенную функцию при помощи аналогичного disable_if:
template < class T >
typename boost::disable_if< boost::is_arithmetic< T >, std::string >::type
f(T x)
{
return"generic";
}
Ура, теперь все работает!
До тех пор, пока мы не обнаружим, что у нас есть замечательный алгоритм для целых чисел, и нам срочно нужно его заюзать!
Мы радостно пишем
template < class T >
typename boost::enable_if< boost::is_integral< T >, std::string >::type
f(T x)
{
return"integral";
}
И... напарываемся на те же неоднозначности, но уже для целочисленных типов. Теперь нам нужно написать disable_if в арифметической версии, и целых 2 disable_if в обобщенной. А потом мы решаем добавить перегрузку для символьных типов и идем вешаться.
В чем же корень проблемы? В том, что матчинг у нас неупорядочен, в отличие от pattern matching в, скажем, Haskell — там все сверху вниз.
Стандартный ПМ через enable_if отлично работает в случае, когда условия не пересекаются, например:
if (i<100) ...;
/*else*/if (i>100) ...;
/*else*/if (i==100) ...;
тут мы можем эти ифы расставить в любом порядке, и нам не нужно писать else — результат будет точно такой же, что и без них.
Однако в этом случае:
if (i<0) ...;
else if (i<10) ...;
else if (i<100) ...;
else/*if (true)*/ ...;
мы уже не можем ни опустить else, ни переставить условия местами — результат изменится и очень сильно: во втором случае сработает не то условие, а в первом сработают сразу несколько, что в нашем случае выражается в неоднозначности. В случае перегрузки функций в С++ у нас нормального if-else нету, поэтому приходится переписывать эти условия на набор независимых условий:
if (i<0) ...;
if (i>=0 && i<10) ...;
if (i>=10 && i<100) ...;
if (i>=100) ...;
Внимание, вопрос — можно ли добиться, чтобы у нас было Haskell-gjдобное упорядочение перегрузок, без необходимости повторять одно и то же?
Т.е. если у нас есть некая последовательность условий
// char -> integral -> arithmetic -> alltemplate < class T >
struct Conditions
: boost::mpl::vector< boost::is_same< T, char >
, boost::is_integral< T >
, boost::is_arithmetic< T >
, boost::mpl::identity< boost::mpl::true_ >
>
{};
можно ли написать перегрузки так, чтоб они разрешались именно в этом порядке?
Оказывается, можно, и притом с помощью очень простого, похожего на enable_if, шаблонного заклинания:
// if T is char, then use this overload:template < class T >
typename enable_cond_с< Conditions< T >, 0, const char* >::type
f(T x)
{
return"char";
}
// else if T is integral, then use this overload:template < class T >
typename enable_cond_с< Conditions< T >, 1, std::string >::type
f(T x)
{
return"integral";
}
// else if T is arithmetic, then use this overload:template < class T >
typename enable_cond_с< Conditions< T >, 2, std::string >::type
f(T x)
{
return"arithmetic";
}
// else use this overload:template < class T >
typename enable_cond_с< Conditions< T >, 3, std::string >::type
f(T x)
{
return"generic";
}
Естественно, возвращаемый тип может быть разным, я для разнообразия сделал const char* для первой перегрузки.
Количество шаблонных аргументов также не ограничено одним, что дает возможность реализовывать разные веселые схемы перегрузки (у меня в проекте как раз была такая, поэтому я и начал писать нечто обобщенное).
enable_cond_с следует стандартной схеме именования, т.е. есть enable_cond, enable_cond_c, lazy_enable_cond и lazy_enable_cond_с.
Реализация ниже:
#include <boost/mpl/at.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/find_if.hpp>
#include <boost/mpl/iterator_range.hpp>
// Helper meta-function to take N first elements from Seqtemplate< class Seq, class N >
struct take
{
BOOST_MPL_ASSERT_MSG( boost::mpl::size< Seq >::type::value >= N::type::value
, SEQUENCE_IS_SHORTER_THAN
, (Seq, N) );
typedef typename boost::mpl::begin< Seq >::type first;
typedef typename boost::mpl::advance< first, N >::type last;
typedef typename boost::mpl::iterator_range< first, last > type;
};
// ordered matching metafunctiontemplate< class CondSeq, class Index >
struct chain_cond_match
{
typedef typename take< CondSeq, Index >::type prev;
typedef typename
boost::mpl::and_< typename boost::mpl::at< CondSeq, Index >::type
, boost::is_same< typename boost::mpl::find_if< prev, boost::mpl::_ >::type
, typename boost::mpl::end< prev >::type
> >::type type;
};
// enable_cond familytemplate< class CondSeq, class Index, class Ret >
struct enable_cond
: boost::enable_if< typename chain_cond_match< CondSeq, Index >::type, Ret >
{};
template< class CondSeq, int i, class Ret >
struct enable_cond_c
: enable_cond< CondSeq, boost::mpl::int_< i >, Ret >
{};
template< class CondSeq, class Index, class Ret >
struct lazy_enable_cond
: boost::lazy_enable_if< typename chain_cond_match< CondSeq, Index >::type, Ret >
{};
template< class CondSeq, int i, class Ret >
struct lazy_enable_cond_c
: lazy_enable_cond< CondSeq, boost::mpl::int_< i >, Ret >
{};
Обратите внимание на BOOST_MPL_ASSERT_MSG — если кто не знал о существовании этого замечательного макроса: я всех призываю его использовать, он дает очень читабельные сообщения об ошибках, типа (правда, это после моего собственного постпроцессора ошибок):
чтобы уменьшить синтаксический оверхед я бы еще замутил пару фичек:
1) дабы не писать в обязательом порядке последним элементом сиквенса условий boost::mpl::identity, может луче его push_back'ать в chain_cond_match перед тем как начать основные действия... все равно он па любому должен быть последним... и более того буит оч неприятно разбирать километры спама от компилятора если его случайно забыть
template< class CondSeq, class Index >
struct chain_cond_match
{
typedef typename boost::mpl::push_back<CondSeq, boost::mpl::identity< boost::mpl::true_> >::type seq_with_default;
typedef typename take< seq_with_default, Index >::type prev;
typedef typename
boost::mpl::and_< typename boost::mpl::at< seq_with_default, Index >::type
, boost::is_same< typename boost::mpl::find_if< prev, boost::mpl::_ >::type
, typename boost::mpl::end< prev >::type
> >::type type;
};
2) не оч сложно избавиться от необходимости создавать свой класс каждый раз когда хочется завести новую пачку условий... lambda нам поможет!
imho выглядит несколько покороче чем создание собственного класса... ну да, слегка изменится синтаксис вызова enable_cond'a... както так:
template < class T >
typename enable_cond_c< conditions, T, 0, const char* >::type
f(T x)
{
return"char";
}
ну и код для проверки условия чуть усложнится на один boost::mpl::apply -- но веть это внутренности реализации )
3) раз уж мы впихиваем identity сами, от чего бы не сделать специализацию enable_cond'a для деволтного значения таким обраазом чтоб не приходилось писать индекс условия!?... ну типа дефольный параметр равный сиквенс сайзу (+1, ибо мы веть теперь сами добавляем дефольный кондишин %)
Здравствуйте, zaufi, Вы писали:
Z>чтобы уменьшить синтаксический оверхед я бы еще замутил пару фичек:
Z>1) дабы не писать в обязательом порядке последним элементом сиквенса условий boost::mpl::identity, может луче его push_back'ать в chain_cond_match перед тем как начать основные действия... все равно он па любому должен быть последним... и более того буит оч неприятно разбирать километры спама от компилятора если его случайно забыть
Я так и написал вначале, а потом в своих задачах обнаружил, что он нужен далеко не всегда (ты ведь не всегда пишешь просто else, написав if).
Например, если у тебя в принципе нет функции, которая принимает любые параметры, а есть только для арифметических + специализации для интегральных.
Так что я решил, что лучше пусть он будет явным.
Z>2) не оч сложно избавиться от необходимости создавать свой класс каждый раз когда хочется завести новую пачку условий... lambda нам поможет! Z>imho выглядит несколько покороче чем создание собственного класса... ну да, слегка изменится синтаксис вызова enable_cond'a... както так:
Z>
Опять же, я так и сделал вначале (оно даже лучше выглядело, потому что я в запихал в макросы), но мне в моих задачах были нужны условия на нескольких типах. В случае с лямбдой придется городить pack/unpack либо делать кучу специализаций enable_cond для разного количества аргументов...
Ну и разницы между классом и тайпдефом я не вижу, если честно — и там, и там ты вводишь новое имя.
Z>3) раз уж мы впихиваем identity сами, от чего бы не сделать специализацию enable_cond'a для деволтного значения таким обраазом чтоб не приходилось писать индекс условия!?... ну типа дефольный параметр равный сиквенс сайзу (+1, ибо мы веть теперь сами добавляем дефольный кондишин %)
Можешь показать, что именно ты имеешь в виду?
Я не смог избавиться от индексов так, чтоб не было явной ссылки на предыдущие условия.
Если тебе удалось, покажи!
Здравствуйте, remark, Вы писали:
R>Здравствуйте, jazzer, Вы писали:
J>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду.
R>jazzer жжот
Здравствуйте, jazzer, Вы писали:
J>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду.
А интересно, можно ли вообще раздавать статические приоритеты сигнатурам, не вытаскивая таблицу принятия решений в отдельное место?
В хаскелле-то не россыпь функций вводится, а просто синтаксический сахар для централизованного паттерн-матчинга.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, jazzer, Вы писали:
J>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду.
К>А интересно, можно ли вообще раздавать статические приоритеты сигнатурам, не вытаскивая таблицу принятия решений в отдельное место? К>В хаскелле-то не россыпь функций вводится, а просто синтаксический сахар для централизованного паттерн-матчинга.
Здравствуйте, jazzer, Вы писали:
J>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду.
Чёто не вижу слона. is_arithmetic и is_integral — пересекаемые условия и в случае пересечения у вас будет двойное объявление одного и того же шаблона. Почему во втором случае не будет неоднозначности?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, remark, Вы писали:
J>>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду.
R>>jazzer жжот
R>Ладно, мы ещё померяемся, у кого шаблон длиннее
Здравствуйте, Vain, Вы писали:
J>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду. V>Чёто не вижу слона. is_arithmetic и is_integral — пересекаемые условия и в случае пересечения у вас будет двойное объявление одного и того же шаблона. Почему во втором случае не будет неоднозначности?
Его решение как раз и предназначено для обхода этой неоднозначности.
Здравствуйте, remark, Вы писали:
J>>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду. V>>Чёто не вижу слона. is_arithmetic и is_integral — пересекаемые условия и в случае пересечения у вас будет двойное объявление одного и того же шаблона. Почему во втором случае не будет неоднозначности? R>Его решение как раз и предназначено для обхода этой неоднозначности.
Я и прошу разъяснить — каким образом?
R>
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, zaufi, Вы писали:
Z>>чтобы уменьшить синтаксический оверхед я бы еще замутил пару фичек:
Z>>1) дабы не писать в обязательом порядке последним элементом сиквенса условий boost::mpl::identity, может луче его push_back'ать в chain_cond_match перед тем как начать основные действия... все равно он па любому должен быть последним... и более того буит оч неприятно разбирать километры спама от компилятора если его случайно забыть
J>Я так и написал вначале, а потом в своих задачах обнаружил, что он нужен далеко не всегда (ты ведь не всегда пишешь просто else, написав if). J>Например, если у тебя в принципе нет функции, которая принимает любые параметры, а есть только для арифметических + специализации для интегральных. J>Так что я решил, что лучше пусть он будет явным.
ну это не страшно совершенно... если не хочется писать дженериковую функцию -- не пиши! -- получишь просто `no matching function call' если попытаешься ее вызвать...
Z>>2) не оч сложно избавиться от необходимости создавать свой класс каждый раз когда хочется завести новую пачку условий... lambda нам поможет! Z>>imho выглядит несколько покороче чем создание собственного класса... ну да, слегка изменится синтаксис вызова enable_cond'a... както так:
Z>>
J>Опять же, я так и сделал вначале (оно даже лучше выглядело, потому что я в запихал в макросы), но мне в моих задачах были нужны условия на нескольких типах. В случае с лямбдой придется городить pack/unpack либо делать кучу специализаций enable_cond для разного количества аргументов...
да лана прям таки городить... вот я тут накидал "на коленке":
#include <boost/mpl/and.hpp>
#include <boost/mpl/apply.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/deref.hpp>
#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/find_if.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/mpl/is_sequence.hpp>
#include <boost/mpl/iterator_range.hpp>
#include <boost/mpl/or.hpp>
#include <boost/mpl/push_back.hpp>
#include <boost/mpl/size.hpp>
#include <boost/mpl/unpack_args.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/type_traits/is_arithmetic.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/utility/enable_if.hpp>
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <string>
#include <typeinfo>
#include <vector>
#include <cxxabi.h>
template <typename T>
inline std::string type_name(const T&)
{
const char* const name = typeid(T).name();
// Try to demangle name using gcc API (of couse if current compiler is gcc)int status;
char* demangled_name = abi::__cxa_demangle(name, 0, 0, &status);
if (!status)
{
std::string name = demangled_name;
free(demangled_name);
return name;
}
assert(!"How is that possible? Fail to demangle name produced by itself??");
return name;
}
struct always_true_precondition
{
template <typename T>
struct apply
{
typedef boost::mpl::true_ type;
};
};
template <typename P, typename T>
struct check_predicate
{
typedef typename boost::mpl::apply<
typename boost::mpl::eval_if<
boost::mpl::is_sequence<T>
, boost::mpl::identity<boost::mpl::unpack_args<P> >
, boost::mpl::identity<P>
>::type
, T
>::type type;
};
// Helper meta-function to take N first elements from Seqtemplate <typename Seq, typename N>
struct take
{
BOOST_MPL_ASSERT_MSG(
boost::mpl::size<Seq>::type::value >= N::type::value
, SEQUENCE_IS_SHORTER_THAN
, (Seq, N)
);
typedef typename boost::mpl::begin<Seq>::type first;
typedef typename boost::mpl::advance<first, N>::type last;
typedef typename boost::mpl::iterator_range<first, last> type;
};
// ordered matching metafunctiontemplate <typename CondSeq, typename T, typename Index>
struct chain_cond_match
{
typedef typename boost::mpl::push_back<
CondSeq
, always_true_precondition
>::type seq_with_default;
typedef typename take<seq_with_default, Index>::type prev;
typedef typename boost::mpl::find_if<
prev
, check_predicate<boost::mpl::_, T>
>::type iter;
typedef typename boost::mpl::apply<
typename boost::mpl::eval_if<
boost::mpl::is_sequence<T>
, boost::mpl::identity<
boost::mpl::unpack_args<
typename boost::mpl::at<seq_with_default, Index>::type
>
>
, boost::mpl::at<seq_with_default, Index>
>::type
, T
>::type predicate_result;
typedef typename boost::is_same<
iter
, typename boost::mpl::end<prev>::type
>::type iter_comp_result;
typedef typename boost::mpl::and_<predicate_result, iter_comp_result>::type type;
};
// enable_cond familytemplate <typename CondSeq, typename T, typename Index, typename Ret>
struct enable_cond : boost::enable_if<typename chain_cond_match<CondSeq, T, Index>::type, Ret>
{};
template <typename CondSeq, typename T, int i, typename Ret>
struct enable_cond_c
: enable_cond<CondSeq, T, boost::mpl::int_<i>, Ret>
{};
typedef boost::mpl::vector<
boost::is_same<boost::mpl::_, char>
, boost::is_integral<boost::mpl::_>
, boost::is_arithmetic<boost::mpl::_>
> conditions;
// if T is char, then use this overload:template <typename T>
typename enable_cond_c<conditions, T, 0, const char*>::type
f(T x)
{
return"char";
}
// else if T is integral, then use this overload:template <typename T>
typename enable_cond_c<conditions, T, 1, std::string>::type
f(T x)
{
return"integral";
}
// else if T is arithmetic, then use this overload:template <typename T>
typename enable_cond_c<conditions, T, 2, std::string>::type
f(T x)
{
return"arithmetic";
}
// else use this overload: TRY TO COMMENT IT OUT...template <typename T>
typename enable_cond_c<conditions, T, 3, std::string>::type
f(T x)
{
return"generic";
}
struct test {};
// (*)typedef boost::mpl::vector<
boost::mpl::or_<
boost::is_same<boost::mpl::_1, char>
, boost::is_same<boost::mpl::_2, char>
>
, boost::mpl::and_<
boost::is_integral<boost::mpl::_1>
, boost::is_same<
boost::mpl::_2
, std::vector<boost::mpl::_1, std::allocator<boost::mpl::_1> >
>
>
, boost::mpl::and_<
boost::is_arithmetic<boost::mpl::_2>
, boost::is_integral<boost::mpl::_3>
, boost::is_same<boost::mpl::_1, test>
>
> binary_conditions;
template <typename T, typename U>
typename enable_cond_c<binary_conditions, boost::mpl::vector<T, U>, 0, std::string >::type
b(T x, U z)
{
return"one of args is char";
}
template <typename T, typename U>
typename enable_cond_c<binary_conditions, boost::mpl::vector<T, U>, 1, std::string >::type
b(T x, U z)
{
return"integral and vector";
}
template <typename T, typename U, typename V>
typename enable_cond_c<binary_conditions, boost::mpl::vector<T, U, V>, 2, const char* const>::type
b(U x, V z)
{
return"test + integral + arithmetic";
}
int main()
{
using namespace std;
cout << f(int(0)) << endl;
cout << f(double(0)) << endl;
cout << f(float(0)) << endl;
cout << f('f') << endl;
cout << f(std::string("fuck")) << endl;
cout << f(test()) << endl;
cout << b('a', 'b') << endl;
cout << b('a', 123) << endl;
cout << b(123, 'b') << endl;
cout << b(123, std::vector<int>()) << endl;
cout << b<test>(3.14, 345) << endl;
}
пример функций с несколькими параметрами начинается от // (*)
если когото пугает что несколько параметров приходится упаковывать в mplный сиквенс, пусть напишет (сгенерит) на BOOST_PREPROCESSOR несколько версий enable_cond... но я чесс гря бы не парился -- нам ли боятся MPL! сиквенсы выглядят вполне себе понятно imho...
J>Ну и разницы между классом и тайпдефом я не вижу, если честно — и там, и там ты вводишь новое имя.
Z>>3) раз уж мы впихиваем identity сами, от чего бы не сделать специализацию enable_cond'a для деволтного значения таким обраазом чтоб не приходилось писать индекс условия!?... ну типа дефольный параметр равный сиквенс сайзу (+1, ибо мы веть теперь сами добавляем дефольный кондишин %)
J>Можешь показать, что именно ты имеешь в виду? J>Я не смог избавиться от индексов так, чтоб не было явной ссылки на предыдущие условия. J>Если тебе удалось, покажи!
да не я просто имел ввиду дефолтный параметр для индекса взять равным индексу последнего (нами же внутри добавляемого always_true_precondition'a) -- убрать индексы совсем нифига не просто (и хотя я глубоко не копал тему, не думаю что возможно)
zaufi@zaufi /work/tests $ g++ --version
g++ (Gentoo 4.4.3 p1.0) 4.4.3
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
zaufi@zaufi /work/tests $ uname -a
Linux zaufi 2.6.32-gentoo-r5 #1 SMP PREEMPT Thu Feb 18 02:49:09 MSK 2010 x86_64 Intel(R) Core(TM)2 Duo CPU T7300 @ 2.00GHz GenuineIntel GNU/Linux
а вот что будет если нет дженериковой функции:
zaufi@zaufi /work/tests $ g++ -o enable_cond enable_cond.cc
enable_cond.cc: In function 'int main()':
enable_cond.cc:218: error: no matching function for call to 'f(std::string)'
enable_cond.cc:219: error: no matching function for call to 'f(test)'
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, remark, Вы писали:
J>>>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду. V>>>Чёто не вижу слона. is_arithmetic и is_integral — пересекаемые условия и в случае пересечения у вас будет двойное объявление одного и того же шаблона. Почему во втором случае не будет неоднозначности? R>>Его решение как раз и предназначено для обхода этой неоднозначности. V>Я и прошу разъяснить — каким образом?
Я уберу boost::mpl, ::type и typename, чтоб было читабельнее:
template< class CondSeq, class Index >
struct chain_cond_match
{
typedef take< CondSeq, Index > prev; // CondSeq[0:Index]typedef
and_< at< CondSeq, Index > // CondSeq[Index]
, is_same< find_if< prev, _ >, end< prev > // find_if(prev, _1) == prev.end()
> > type;
};
MPL представляет собой STL, только на типах
Т.е. find_if вернет end, если ничего не найдет.
prev содержит список условий, которые НЕ должны выполниться.
Я ищу в prev условие, которое выполнится (туда надо передавать предикат, но в нашем случае само условие и есть предикат, поэтому я просто отдаю _), и сравниваю результат с end — если получился end, значит, ни одно условие из prev не выполнилось.
И этот результат мы объединяем через and с нашим текущим условием.
Здравствуйте, jazzer, Вы писали:
J>>>>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду. V>>>>Чёто не вижу слона. is_arithmetic и is_integral — пересекаемые условия и в случае пересечения у вас будет двойное объявление одного и того же шаблона. Почему во втором случае не будет неоднозначности? R>>>Его решение как раз и предназначено для обхода этой неоднозначности. V>>Я и прошу разъяснить — каким образом? J>Я уберу boost::mpl, ::type и typename, чтоб было читабельнее: J>И этот результат мы объединяем через and с нашим текущим условием.
Т.е. получается некоторая перегрузка возвращаемого типа во время вызова? Вот здесь и есть проблема, непонятно почему компилятор разрешает такое...
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, jazzer, Вы писали:
J>>>>>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду. V>>>>>Чёто не вижу слона. is_arithmetic и is_integral — пересекаемые условия и в случае пересечения у вас будет двойное объявление одного и того же шаблона. Почему во втором случае не будет неоднозначности? R>>>>Его решение как раз и предназначено для обхода этой неоднозначности. V>>>Я и прошу разъяснить — каким образом? J>>Я уберу boost::mpl, ::type и typename, чтоб было читабельнее: J>>И этот результат мы объединяем через and с нашим текущим условием.
V>Т.е. получается некоторая перегрузка возвращаемого типа во время вызова? Вот здесь и есть проблема, непонятно почему компилятор разрешает такое...
Поточу что ему явно сказали, что вот эта функция для типа Т, который is_integral; а вот эта для типа Т, который is_arithmetic, но не is_integral. Тут нет никакой неоднозначности и двойного объявления.
Здравствуйте, remark, Вы писали:
R>Здравствуйте, Vain, Вы писали:
V>>Здравствуйте, jazzer, Вы писали:
J>>>>>>>Ну, может, не совсем pattern matching, ниже будет понятно, что я имею в виду. V>>>>>>Чёто не вижу слона. is_arithmetic и is_integral — пересекаемые условия и в случае пересечения у вас будет двойное объявление одного и того же шаблона. Почему во втором случае не будет неоднозначности? R>>>>>Его решение как раз и предназначено для обхода этой неоднозначности. V>>>>Я и прошу разъяснить — каким образом? J>>>Я уберу boost::mpl, ::type и typename, чтоб было читабельнее: J>>>И этот результат мы объединяем через and с нашим текущим условием. V>>Т.е. получается некоторая перегрузка возвращаемого типа во время вызова? Вот здесь и есть проблема, непонятно почему компилятор разрешает такое... R>Поточу что ему явно сказали, что вот эта функция для типа Т, который is_integral; а вот эта для типа Т, который is_arithmetic, но не is_integral. Тут нет никакой неоднозначности и двойного объявления.
Я правильно понял, что все функции на момент вызова, кроме одной задизейблены?
R>
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
J> // char -> integral -> arithmetic -> all
J> template < class T >
J> struct Conditions
J> : boost::mpl::vector< boost::is_same< T, char >
J> , boost::is_integral< T >
J> , boost::is_arithmetic< T >
J> , boost::mpl::identity< boost::mpl::true_ >
>
J> {};
J> // if T is char, then use this overload:
J> template < class T >
J> typename enable_cond_с< Conditions< T >, 0, const char* >::type
J> f(T x)
J> {
J> return"char";
J> }
J> // else if T is integral, then use this overload:
J> template < class T >
J> typename enable_cond_с< Conditions< T >, 1, std::string >::type
J> f(T x)
J> {
J> return"integral";
J> }
J> // else if T is arithmetic, then use this overload:
J> template < class T >
J> typename enable_cond_с< Conditions< T >, 2, std::string >::type
J> f(T x)
J> {
J> return"arithmetic";
J> }
J> // else use this overload:
J> template < class T >
J> typename enable_cond_с< Conditions< T >, 3, std::string >::type
J> f(T x)
J> {
J> return"generic";
J> }
То же, но обходясь средствами, которые предоставляет Boost:
#include <iostream>
#include <string>
#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
template < class T >
typename boost::disable_if< boost::is_same< T, char >, std::string >::type
f_integral(T x)
{
return"integral";
}
template < class T >
typename boost::enable_if< boost::is_same< T, char >, const char* >::type
f_integral(T x)
{
return"char";
}
template < class T >
typename boost::disable_if< boost::is_integral< T >, std::string >::type
f_arithmetic(T x)
{
return"arithmetic";
}
template < class T >
typename boost::enable_if< boost::is_integral< T >, std::string >::type
f_arithmetic(T x)
{
return f_integral(x);
}
template < class T >
typename boost::disable_if< boost::is_arithmetic< T >, std::string >::type
f(T x)
{
return"generic";
}
template < class T >
typename boost::enable_if< boost::is_arithmetic< T >, std::string >::type
f(T x)
{
return f_arithmetic(x);
}
int main()
{
std::cout
<< f('a') << '\n'
<< f(1) << '\n'
<< f(1.) << '\n'
<< f("") << '\n'
;
}
На две строки длиннее, зато комментарии не нужны, поскольку нет никаких ничего не говорящих индексов 0, 1, 2, 3. И можно не только последовательность, но и дерево условий использовать. Ну и Бритва Оккама к тому же.
I>То же, но обходясь средствами, которые предоставляет Boost:
да никто и не говорил что это не возможно...
но согласись в твоем коде логика disable/enable не столь очевидна...
я посмотрев по диагонале понял что для того чтоб разобраться в этом надо садиться с ручкой+бумажкой и вырисовывать все кейсы...
кроме того я бы не хотел быть тем чуваком которому понадобится добавить пару-тройку "перегрузок" сюда...
I>На две строки длиннее, зато комментарии не нужны, поскольку нет никаких ничего не говорящих индексов 0, 1, 2, 3. И можно не только последовательность, но и дерево условий использовать. Ну и Бритва Оккама к тому же.
ну они такие же ничего не говорящие как и индексы в tuple... (и даже в отличии от tuple используются примерно один раз, и все таки несмотря на это ничего не говорящие индексы по прежнему могут быть наменены на enum, или typedef в случае enable_cond)
enable_cond чемта похож на tuple: список условий объявляется в одном месте (и довольно прост для изменения), далее просто ссылаемся на условие по индексу...