我很想从std::begin
.
CRTP 很棒,但它要求每个结构都进行自我修改以处理您对 c 的要求。实际上,c 的代码是您的问题,而不是您所提供的数据的问题。
自然,您会想要零开销,CRTP 和这种方法都可以实现。
因此,我们有条件地调用.c()
或.a()+.b()
根据它的存在调用。这里有两种方法:
创建一个自由函数c
:
template<class T, class...Ignored>
decltype(auto) c(T& t, Ignored&&...)
它分派给两个实现:
{
auto which = has_c_method<T>;
return details::c(which{}, t);
}
wherehas_c_method
是一个 traits bool 类型,用于检测传递的类型是否有.c()
方法。(我在下面写一个)。
在命名空间详细信息中:
namespace details{
template<class T>
auto c(std::false_type, T&){
return t.a()-t.b();
}
template<class T>
auto c(std::true_type, T&){
return t.c();
}
}
我们很好。c(t)
另请注意,如果 s 命名空间中有一个免费的非可变函数t
,它将是首选的(就是这样Ignored
做的)。
您确实必须编写该特征类,但是许多 SO 答案都涵盖了这一点。
一个比c
建议更好的名字。;)
这种设计的优点是不会强迫编写目标类型的人参与操作。您只需访问t.c()
或t.a()+t.b()
根据是否t.c()
定义。
现在我们可以从一个更通用的方向来解决这个问题。我们不会创建一个c
为我们调度的函数,而是......
我们编写一个编译时分支:
namespace details {
template<bool>
struct branch {
template<class T, class F_true, class F_false>
std::result_of_t<F_true(T)> operator()( T&&t, F_true&&f, F_false&&){
return decltype(f)(f)(decltype(t)(t));
}
};
template<>
struct branch<false> {
template<class T, class F_true, class F_false>
std::result_of_t<F_false(T)> branch( T&& t, F_true&&, F_false&&f){
return decltype(f)(f)(decltype(t)(t));
}
};
}
template<template<class...>class Z, class T, class F_true, class F_false>
auto branch( T&& t, F_true&& f_true, F_false&& f_false )
-> decltype( details::branch<Z<T>{}>{}(std::declval<T>(), std::declval<F_true>(), std::declval<F_false>() )
{
return details::branch<Z<T>{}>{}(decltype(t)(t), decltype(f_true)(f_true), decltype(f_false)(f_false) );
}
没有虚假案例:
template<template<class...>class Z, class T, class F_true>
void branch( T&& t, F_true&& f_true )
{
branch( std::forward<T>(t), std::forward<F_true>(f_true), [](auto&&){} );
}
利用:
int c = branch<has_c_method>(
t,
[&](auto& t){ return t.c(); },
[&](auto& t){ return t.a()-t.b(); }
);
这使您可以更临时地执行此操作。
branch<template>( arg, if_true, if_false )
评估template
类型(包括 r/l 值限定)arg
。如果结果类型的实例在 constexpr 上下文中返回 true,if_true
则运行。如果它在 constexpr 竞赛中返回 false,if_false
则运行。
在这两种情况下,arg
都将传递给选定的 lambda。
与auto
C++14 的 lambda 支持一起,这使您可以编写相对简洁的有条件编译的代码。
未运行的 lambda 只是一个未实例化的模板。run lambda 使用 arg 的实例进行实例化。因此,未运行的 lambda 在未选择的情况下不需要包含有效代码。
的类型branch
实际上是在两个选项之间静态选择的;他们实际上可以返回不同的类型。没有进行转换。
return 的 if_false-less 重载branch
,void
因为我很懒,而且我看不到太多用处。
has_c_method
这是用大部分通用代码编写的草图。
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply_helper:
std::false_type
{};
template<template<class...>class Z, class...Ts>
struct can_apply_helper<Z, std::void_t<Z<Ts...>>, Ts...>:
std::true_type
{};
}
// is true_type iff Z<Ts...> is valid:
template<template<class...>class Z, class...Ts>
using can_apply = typename details::can_apply_helper<Z, void, Ts...>::type;
// return type of t.c(args...). Easy to write
// and with the above, makes has_c_method a one-liner:
template<class T, class...Args>
using c_method_result = decltype(std::declval<T>().c(std::declval<Args>()...));
template<class T, class...Args>
using has_c_method = can_apply<c_method_result, T, Args...>;
有一个建议添加一些非常喜欢can_apply
的东西std
。
请注意我对上述的非惯用用法decltype(x)(x)
。这等效于std::forward<X>(x)
在 whereX
是转发引用的上下文中,并且也适用于auto&&
参数 lambdas。这意味着“x
转换为声明它的类型”。请注意,如果x
是值(非引用)类型,它将复制它(这是首选转发的原因,它永远不会这样做):但是,在我的上述任何decltype(x)(x)
用途中都不是这种情况。