元程序化:函数定义的失败定义了一个独立的函数
在这个答案中,我定义了一个基于types的is_arithmetic
属性的模板:
template<typename T> enable_if_t<is_arithmetic<T>::value, string> stringify(T t){ return to_string(t); } template<typename T> enable_if_t<!is_arithmetic<T>::value, string> stringify(T t){ return static_cast<ostringstream&>(ostringstream() << t).str(); }
dyp表明 ,而不是该types的is_arithmetic
属性,是否为该types定义了to_string
是模板select条件。 这显然是可取的,但我不知道这样说:
如果未定义
std::to_string
则使用ostringstream
重载。
声明to_string
条件很简单:
template<typename T> decltype(to_string(T{})) stringify(T t){ return to_string(t); }
这是与我无法弄清楚如何构build的标准相反的。 这显然不起作用,但希望它传达了我想要构build的东西:
template<typename T> enable_if_t<!decltype(to_string(T{})::value, string> (T t){ return static_cast<ostringstream&>(ostringstream() << t).str(); }
在上周的委员会会议上刚刚投票进入图书馆基本面TS:
template<class T> using to_string_t = decltype(std::to_string(std::declval<T>())); template<class T> using has_to_string = std::experimental::is_detected<to_string_t, T>;
然后在has_to_string
上标记调度和/或SFINAE到你心中的内容。
您可以参考当前TS的工作草案,了解is_detected
以及如何is_detected
朋友。 这与@Yakk的答案中的can_apply
很相似。
使用void_t
的void_t
:
template <typename...> using void_t = void;
制作这样一个types的特征是很容易的:
template<typename T, typename = void> struct has_to_string : std::false_type { }; template<typename T> struct has_to_string<T, void_t<decltype(std::to_string(std::declval<T>()))> > : std::true_type { };
首先,我认为SFINAE通常应该被隐藏起来。 它使界面凌乱。 将SFINAE从表面上移开,并使用标签调度select过载。
其次,我甚至把SFINAE从特质类中隐藏起来。 写作“我可以做X”代码在我的经验中是很常见的,我不想写乱七八糟的SFINAE代码来做这件事。 所以相反,我写了一个通用的can_apply
特质,并且如果使用decltype
传递错误的types,就会出现SFINAE失败的特性。
然后,我们将SFIANE失败的decltype
特征提供给can_apply
,并根据应用程序是否失败获取true / falsetypes。
这样可以将每个“我可以做X”特性的工作减less到最低限度,并且将SFINAE代码的一些棘手和脆弱的部分从日常工作中解放出来。
我使用C ++ 1z的void_t
。 自己实现它很容易(在这个答案的底部)。
一个类似于can_apply
的元函数被提出用于C ++ 1z中的标准化,但它不像void_t
那样稳定,所以我没有使用它。
首先,一个隐藏can_apply
的实现的details
命名空间被偶然发现:
namespace details { template<template<class...>class Z, class, class...> struct can_apply:std::false_type{}; template<template<class...>class Z, class...Ts> struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>: std::true_type{}; }
然后,我们可以用can_apply
的details::can_apply
来编写can_apply
,它有一个更好的接口(它不需要传递额外的void
):
template<template<class...>class Z, class...Ts> using can_apply=details::can_apply<Z, void, Ts...>;
以上是通用的帮助程序元编程代码。 一旦我们有了它,我们可以非常干净地写一个can_to_string
traits类:
template<class T> using to_string_t = decltype( std::to_string( std::declval<T>() ) ); template<class T> using can_to_string = can_apply< to_string_t, T >;
我们有一个特性can_to_string<T>
,如果我们能够把一个T
can_to_string<T>
它,那么就是真的。
这个工作需要编写一个新的特性,就是现在的2-4行简单的代码 – 只需要using
别名进行decltype
,然后对其进行can_apply
testing。
一旦我们有了这个,我们使用标签调度来正确的实现:
template<typename T> std::string stringify(T t, std::true_type /*can to string*/){ return std::to_string(t); } template<typename T> std::string stringify(T t, std::false_type /*cannot to string*/){ return static_cast<ostringstream&>(ostringstream() << t).str(); } template<typename T> std::string stringify(T t){ return stringify(t, can_to_string<T>{}); }
所有丑陋的代码都隐藏在details
名称空间中。
如果你需要一个void_t
,使用这个:
template<class...>struct voider{using type=void;}; template<class...Ts>using void_t=typename voider<Ts...>::type;
在大多数主要的C ++ 11编译器中都可以工作。
请注意,更简单的template<class...>using void_t=void;
无法在一些较旧的C ++ 11编译器中工作(标准中有一个模棱两可的问题)。
你可以使用expression式SFINAE为此写一个帮手特质:
namespace detail { //base case, to_string is invalid template <typename T> auto has_to_string_helper (...) //... to disambiguate call -> false_type; //true case, to_string valid for T template <typename T> auto has_to_string_helper (int) //int to disambiguate call -> decltype(std::to_string(std::declval<T>()), true_type{}); } //alias to make it nice to use template <typename T> using has_to_string = decltype(detail::has_to_string_helper<T>(0));
然后使用std::enable_if_t<has_to_string<T>::value>
演示
我认为有两个问题:1)find给定types的所有可行的algorithm。 2)select最好的一个。
例如,我们可以手动指定一组重载algorithm的顺序:
namespace detail { template<typename T, REQUIRES(helper::has_to_string(T))> std::string stringify(choice<0>, T&& t) { using std::to_string; return to_string(std::forward<T>(t)); } template<std::size_t N> std::string stringify(choice<1>, char const(&arr)[N]) { return std::string(arr, N); } template<typename T, REQUIRES(helper::has_output_operator(T))> std::string stringify(choice<2>, T&& t) { std::ostringstream o; o << std::forward<T>(t); return std::move(o).str(); } }
第一个函数参数指定这些algorithm之间的顺序(“第一select”,“第二select”,…)。 为了select一个algorithm,我们简单地派遣到最好的可行的匹配:
template<typename T> auto stringify(T&& t) -> decltype( detail::stringify(choice<0>{}, std::forward<T>(t)) ) { return detail::stringify(choice<0>{}, std::forward<T>(t)); }
这是如何实施的? 我们从Xeo @ Flaming Dangerzone偷了一下, Paul @ void_t
“能实现概念”吗? (使用简化的实现):
constexpr static std::size_t choice_max = 10; template<std::size_t N> struct choice : choice<N+1> { static_assert(N < choice_max, ""); }; template<> struct choice<choice_max> {}; #include <type_traits> template<typename T, typename = void> struct models : std::false_type {}; template<typename MF, typename... Args> struct models<MF(Args...), decltype(MF{}.requires_(std::declval<Args>()...), void())> : std::true_type {}; #define REQUIRES(...) std::enable_if_t<models<__VA_ARGS__>::value>* = nullptr
select类inheritance了更糟糕的select: choice<0>
inheritance自choice<1>
。 因此,对于typeschoice<0>
的参数,typeschoice<0>
的函数参数比choice<1>
更好匹配,这比choice<2>
更好地匹配,等等[over.ics.rank ] P4.4
请注意, 更专业的平局只有两个function都不好时才适用。 由于choice
总的顺序,我们永远不会陷入这种情况。 这可以防止调用不明确,即使多个algorithm是可行的。
我们定义我们的types特征:
#include <string> #include <sstream> namespace helper { using std::to_string; struct has_to_string { template<typename T> auto requires_(T&& t) -> decltype( to_string(std::forward<T>(t)) ); }; struct has_output_operator { std::ostream& ostream(); template<typename T> auto requires_(T&& t) -> decltype(ostream() << std::forward<T>(t)); }; }
macros可以通过使用R. Martinho Fernandes的想法来避免:
template<typename T> using requires = std::enable_if_t<models<T>::value, int>; // exemplary application: template<typename T, requires<helper::has_to_string(T)> = 0> std::string stringify(choice<0>, T&& t) { using std::to_string; return to_string(std::forward<T>(t)); }
那么,你可以跳过所有的元编程魔术,并使用Fit库中的fit::conditional
适配器:
FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional( [](auto x) -> decltype(to_string(x)) { return to_string(x); }, [](auto x) -> decltype(static_cast<ostringstream&>(ostringstream() << x).str()) { return static_cast<ostringstream&>(ostringstream() << x).str(); } );
或者更简洁,如果你不介意macros:
FIT_STATIC_LAMBDA_FUNCTION(stringify) = fit::conditional( [](auto x) FIT_RETURNS(to_string(x)), [](auto x) FIT_RETURNS(static_cast<ostringstream&>(ostringstream() << x).str()) );
注意,我也限制了第二个函数,所以如果types不能用to_string
调用,也不能用ostringstream
stream,那么函数就不能被调用。 这有助于更好的错误消息和更好的组合性与检查types的要求。