元程序化:函数定义的失败定义了一个独立的函数

在这个答案中,我定义了一个基于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_tvoid_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_applydetails::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_applytesting。

一旦我们有了这个,我们使用标签调度来正确的实现:

 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调用,也不能用ostringstreamstream,那么函数就不能被调用。 这有助于更好的错误消息和更好的组合性与检查types的要求。