Метапрограммирование: отказ определения функции Определяет отдельную функцию
В этом ответе я определяю шаблон, основанный на свойстве is_arithmetic
типа:
template enable_if_t<is_arithmetic::value, string> stringify(T t){ return to_string(t); } template enable_if_t<!is_arithmetic::value, string> stringify(T t){ return static_cast(ostringstream() << t).str(); }
dyp предполагает, что вместо свойства is_arithmetic
этого типа определяется, является ли to_string
для типа критерием выбора шаблона. Это явно желательно, но я не знаю, как сказать:
Если
std::to_string
не определен, используйте перегрузкуostringstream
.
Объявление критериев to_string
прост:
template decltype(to_string(T{})) stringify(T t){ return to_string(t); }
Это противоположность этим критериям, что я не могу понять, как построить. Это явно не работает, но, надеюсь, он передает то, что я пытаюсь построить:
template enable_if_t (T t){ return static_cast(ostringstream() << t).str(); }
- Почему шаблон функции не может быть частично специализированным?
- C ++ 11 не выводит тип, когда задействованы функции std :: function или lambda
- Как использовать dom-repeat с объектами вместо массивов в Polymer 1.0?
- Инициализация статического члена в шаблоне classа
- Распространение «typedef» из основанного на производный class для «шаблона»
- понять цель jsf ui: состав
- Препроцессор C ++: избегать повторения кода списка переменных-членов
- Каковы различия между Generics в C # и Java ... и шаблонами в C ++?
Свежее голосование в основах библиотеки TS на собрании комитета на прошлой неделе:
template using to_string_t = decltype(std::to_string(std::declval())); template using has_to_string = std::experimental::is_detected;
Затем отправьте тег и / или SFINAE на has_to_string
в has_to_string
вашего сердца.
Вы можете проконсультироваться с текущим рабочим проектом TS о том, как is_detected
и друзья могут быть реализованы. Это довольно похоже на can_apply
в ответе @ Якка.
Использование void_t
Уолтера Брауна :
template using void_t = void;
Очень легко сделать такую характеристику типа:
template struct has_to_string : std::false_type { }; template struct has_to_string()))> > : std::true_type { };
Во-первых, я думаю, что SFINAE обычно следует скрывать от интерфейсов. Это делает интерфейс грязным. Поместите SFINAE с поверхности и используйте диспетчер меток для перегрузки.
Во-вторых, я даже скрываю SFINAE из classа признаков. Написание кода «могу ли я сделать X» достаточно распространено в моем опыте, что я не хочу писать грязный код SFINAE для этого. Поэтому вместо этого я пишу общий признак can_apply
и имею свойство, которое SFINAE терпит неудачу, если переданы неправильные типы, используя decltype
.
Затем мы передаем decltype
declitype decltype
с can_apply
и can_apply
истинный / ложный тип в зависимости от того, сбой приложения.
Это уменьшает работу на показатель «Я могу сделать X» до минимальной суммы и помещает несколько сложный и хрупкий код SFINAE в изо дня в день.
Я использую void_t
C ++ 1z. Реализация его сама по себе легко (внизу этого ответа).
Metafunction, подобный can_apply
, предлагается для стандартизации в C ++ 1z, но он не такой стабильный, как void_t
, поэтому я его не использую.
Во-первых, пространство имен details
чтобы скрыть реализацию can_apply
от случайного обнаружения:
namespace details { templateclass Z, class, class...> struct can_apply:std::false_type{}; templateclass Z, class...Ts> struct can_apply>, Ts...>: std::true_type{}; }
Затем мы можем написать can_apply
с точки зрения details::can_apply
, и он имеет более приятный интерфейс (для него не требуется details::can_apply
лишней void
):
templateclass Z, class...Ts> using can_apply=details::can_apply;
Вышеприведенный общий метапрограммирующий код. Как только мы получим его, мы можем написать can_to_string
признаков can_to_string
очень чисто:
template using to_string_t = decltype( std::to_string( std::declval() ) ); template using can_to_string = can_apply< to_string_t, T >;
и у нас есть свойство can_to_string
, которое истинно, если мы можем to_string
a T
Для работы требуется написать новую черту, которая теперь составляет 2-4 строки простого кода – просто сделайте decltype
using
псевдонима, а затем выполните тест can_apply
.
Как только мы это получим, мы используем отправку тегов для правильной реализации:
template std::string stringify(T t, std::true_type /*can to string*/){ return std::to_string(t); } template std::string stringify(T t, std::false_type /*cannot to string*/){ return static_cast(ostringstream() << t).str(); } template std::string stringify(T t){ return stringify(t, can_to_string{}); }
Весь уродливый код скрывается в пространстве имен details
.
Если вам нужен void_t
, используйте это:
templatestruct voider{using type=void;}; templateusing void_t=typename voider::type;
который работает в большинстве основных компиляторов C ++ 11.
Обратите внимание, что более простой template
не работает в некоторых старых компиляторах C ++ 11 (в стандарте была двусмысленность).
Вы можете написать вспомогательную черту для этого, используя выражение SFINAE:
namespace detail { //base case, to_string is invalid template auto has_to_string_helper (...) //... to disambiguate call -> false_type; //true case, to_string valid for T template auto has_to_string_helper (int) //int to disambiguate call -> decltype(std::to_string(std::declval()), true_type{}); } //alias to make it nice to use template using has_to_string = decltype(detail::has_to_string_helper(0));
Затем используйте std::enable_if_t
демонстрация
Я думаю, что есть две проблемы: 1) Найти все жизнеспособные алгоритмы для данного типа. 2) Выберите лучший.
Мы можем, например, вручную указать порядок для набора перегруженных алгоритмов:
namespace detail { template std::string stringify(choice<0>, T&& t) { using std::to_string; return to_string(std::forward(t)); } template std::string stringify(choice<1>, char const(&arr)[N]) { return std::string(arr, N); } template std::string stringify(choice<2>, T&& t) { std::ostringstream o; o << std::forward(t); return std::move(o).str(); } }
Первый параметр функции указывает порядок между этими алгоритмами («первый выбор», «второй выбор», ..). Чтобы выбрать алгоритм, мы просто отправляем в наилучшее жизнеспособное соответствие:
template auto stringify(T&& t) -> decltype( detail::stringify(choice<0>{}, std::forward(t)) ) { return detail::stringify(choice<0>{}, std::forward (t)); }
Как это реализовано? Мы крадем немного от Xeo @ Flaming Dangerzone, а Paul @ void_t
«может реализовать концепции»? (с использованием упрощенных реализаций):
constexpr static std::size_t choice_max = 10; template struct choice : choice { static_assert(N < choice_max, ""); }; template<> struct choice {}; #include template struct models : std::false_type {}; template struct models()...), void())> : std::true_type {}; #define REQUIRES(...) std::enable_if_t::value>* = nullptr
Классы выбора наследуют от худших вариантов: choice<0>
наследует choice<1>
. Поэтому для аргумента choice<0>
типа choice<0>
параметр функции choice<0>
типа choice<0>
является лучшим совпадением, чем choice<1>
, что лучше, чем choice<2>
и т. Д. [Over.ics.rank ] P4.4
Обратите внимание, что более специализированный тай-брейкер применяется только в том случае, если ни одна из двух функций не лучше. Из-за общего порядка choice
s мы никогда не столкнемся с этой ситуацией. Это предотвращает неоднозначность вызовов, даже если несколько алгоритмов жизнеспособны.
Мы определяем наши черты типа:
#include #include namespace helper { using std::to_string; struct has_to_string { template auto requires_(T&& t) -> decltype( to_string(std::forward(t)) ); }; struct has_output_operator { std::ostream& ostream(); template auto requires_(T&& t) -> decltype(ostream() << std::forward(t)); }; }
Макросов можно избежать, используя идею Р. Мартиньо Фернандеса :
template using requires = std::enable_if_t::value, int>; // exemplary application: template = 0> std::string stringify(choice<0>, T&& t) { using std::to_string; return to_string(std::forward(t)); }
Ну, вы можете просто пропустить всю магию метапрограммирования и использовать fit::conditional
адаптер из библиотеки Fit :
FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional( [](auto x) -> decltype(to_string(x)) { return to_string(x); }, [](auto x) -> decltype(static_cast(ostringstream() << x).str()) { return static_cast(ostringstream() << x).str(); } );
Или даже более компактный, если вы не возражаете против макросов:
FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional( [](auto x) FIT_RETURNS(to_string(x)), [](auto x) FIT_RETURNS(static_cast(ostringstream() << x).str()) );
Обратите внимание, что я также ограничил вторую функцию, поэтому, если тип не может быть вызван с помощью to_string
или не ostringstream
в ostringstream
тогда функция не может быть вызвана. Это помогает с улучшенными сообщениями об ошибках и лучшей совместимостью с проверкой требований типа.