【发布时间】:2010-11-26 01:12:21
【问题描述】:
我想做类似的事情
template <typename T>
void foo(const T& t) {
IF bar(t) would compile
bar(t);
ELSE
baz(t);
}
我认为使用enable_if 的东西可以在这里完成工作,将foo 分成两部分,但我似乎无法弄清楚细节。实现这一目标的最简单方法是什么?
【问题讨论】:
我想做类似的事情
template <typename T>
void foo(const T& t) {
IF bar(t) would compile
bar(t);
ELSE
baz(t);
}
我认为使用enable_if 的东西可以在这里完成工作,将foo 分成两部分,但我似乎无法弄清楚细节。实现这一目标的最简单方法是什么?
【问题讨论】:
对名称bar 进行了两次查找。一种是在foo 的定义上下文中的非限定查找。另一种是在每个实例化上下文中的参数相关查找(但每个实例化上下文的查找结果不允许改变两个不同实例化上下文之间的行为)。
要获得所需的行为,您可以在 fallback 命名空间中定义一个回退函数,该函数返回一些唯一类型
namespace fallback {
// sizeof > 1
struct flag { char c[2]; };
flag bar(...);
}
如果没有其他匹配项,将调用 bar 函数,因为省略号的转换成本最低。现在,通过 fallback 的 using 指令将候选对象包含到您的函数中,以便将 fallback::bar 作为候选对象包含到对 bar 的调用中。
现在,要查看对bar 的调用是否解析为您的函数,您将调用它,并检查返回类型是否为flag。否则选择的函数的返回类型可能是 void,因此您必须做一些逗号运算符技巧来解决这个问题。
namespace fallback {
int operator,(flag, flag);
// map everything else to void
template<typename T>
void operator,(flag, T const&);
// sizeof 1
char operator,(int, flag);
}
如果我们的函数被选中,那么逗号运算符调用将返回对int 的引用。如果不是,或者如果所选函数返回void,则调用将依次返回void。然后,如果选择了我们的后备,则以 flag 作为第二个参数的下一次调用将返回 sizeof 1 的类型,如果选择了大于 1 的 sizeof(将使用内置逗号运算符,因为 void 混合在一起)如果选择了其他东西。
我们将 sizeof 和 delegate 与一个结构进行比较。
template<bool>
struct foo_impl;
/* bar available */
template<>
struct foo_impl<true> {
template<typename T>
static void foo(T const &t) {
bar(t);
}
};
/* bar not available */
template<>
struct foo_impl<false> {
template<typename T>
static void foo(T const&) {
std::cout << "not available, calling baz...";
}
};
template <typename T>
void foo(const T& t) {
using namespace fallback;
foo_impl<sizeof (fallback::flag(), bar(t), fallback::flag()) != 1>
::foo(t);
}
如果现有函数也有省略号,则此解决方案不明确。但这似乎不太可能。使用后备测试:
struct C { };
int main() {
// => "not available, calling baz..."
foo(C());
}
如果使用参数相关查找找到候选者
struct C { };
void bar(C) {
std::cout << "called!";
}
int main() {
// => "called!"
foo(C());
}
为了在定义上下文中测试非限定查找,让我们在foo_impl和foo之上定义以下函数(将foo_impl模板放在foo之上,因此它们具有相同的定义上下文)
void bar(double d) {
std::cout << "bar(double) called!";
}
// ... foo template ...
int main() {
// => "bar(double) called!"
foo(12);
}
【讨论】:
op, 调用来做到这一点,因为有些类型会重载模板化op, 并且接受标志作为第二个参数(比如del << a, b;,它有一个delim operator,(delim,T const&);)。所以这样做(bar(t), flag()) 会产生歧义。我们也无法交换订单,因为bar(t) 可能有类型void。当然,将op, 接受标志作为第一个参数的可能性要小得多-因此是三向的事情:)
litb has given you a very good answer。但是,我想知道,考虑到更多的上下文,我们是否不能想出一些不那么通用但也更少的东西,嗯,详细说明?
比如T可以是什么类型?任何事物?几种类型?您可以控制的非常受限的集合?您设计的某些类与函数foo 结合使用?鉴于后者,你可以简单地把像
typedef boolean<true> has_bar_func;
进入类型,然后基于此切换到不同的foo 重载:
template <typename T>
void foo_impl(const T& t, boolean<true> /*has_bar_func*/);
template <typename T>
void foo_impl(const T& t, boolean<false> /*has_bar_func*/);
template <typename T>
void foo(const T& t) {
foo_impl( t, typename T::has_bar_func() );
}
另外,bar/baz 函数可以有任何签名,是否有一些限制集,或者只有一个有效的签名?如果是后者,litb 的(优秀的)后备想法,结合使用sizeof 的元函数可能会更简单一些。但是这个我没有探索过,所以只是一个想法。
【讨论】:
swap 函数一样使用 ADL 也很有用。无论如何,谢谢你的赞美:)
bar 的T 集合并不是完全任意的,但它不仅仅是几种类型。另外,为不同类型专门设置has_bar_func 将是(小但重要的)代码重复。虽然 litb 的想法非常聪明,但并不是那么“精心”(我认为 boost 中有很多例子要复杂得多)
struct flag { char c[2]; }; 时,我知道这是你的,尽管你的名字仍然没有出现在屏幕上。 :)
我认为 litb 的解决方案有效,但过于复杂。原因是他引入了一个函数fallback::bar(...),它充当“最后的函数”,然后竭尽全力不调用它。为什么?看来我们有一个完美的行为:
namespace fallback {
template<typename T>
inline void bar(T const& t, ...)
{
baz(t);
}
}
template<typename T>
void foo(T const& t)
{
using namespace fallback;
bar(t);
}
但正如我在对 litb 原始帖子的评论中指出的那样,bar(t) 无法编译的原因有很多,我不确定这个解决方案是否能处理相同的情况。它肯定会在private bar::bar(T t) 上失败
【讨论】:
13.3.2,13.3.3)。
如果您愿意限制自己使用 Visual C++,可以使用 __if_exists 和 __if_not_exists 语句。
在紧要关头很方便,但特定于平台。
【讨论】:
编辑:我说得太早了! litb's answer 展示了如何实际做到这一点(以您的理智为代价... :-P)
不幸的是,我认为检查“是否会编译”的一般情况超出了function template argument deduction + SFINAE 的范围,这是处理这些东西的常用技巧。我认为你能做的最好的就是创建一个“备份”功能模板:
template <typename T>
void bar(T t) { // "Backup" bar() template
baz(t);
}
然后将foo()改为简单:
template <typename T>
void foo(const T& t) {
bar(t);
}
这适用于大多数情况。因为bar() 模板的参数类型是T,与任何其他名为bar() 的函数或函数模板相比,它将被视为“不太专业”,因此在重载期间将优先于该预先存在的函数或函数模板解决。除了:
bar() 本身是一个函数模板,它采用T 类型的模板参数,则会出现歧义,因为这两个模板都不比另一个模板更专业,编译器会报错。bar(long),但调用了foo(123)。在这种情况下,编译器会悄悄地选择用T = int 实例化“备份”bar() 模板,而不是执行int->long 提升,即使后者本来可以编译并正常工作!简而言之:没有简单、完整的解决方案,而且我很确定甚至没有一个棘手的、完整的解决方案。 :(
【讨论】:
//default
//////////////////////////////////////////
template <class T>
void foo(const T& t){
baz(t);
}
//specializations
//////////////////////////////////////////
template <>
void foo(const specialization_1& t){
bar(t);
}
....
template <>
void foo(const specialization_n& t){
bar(t);
}
【讨论】:
bar() 的类型列表来保持专业化列表的最新状态——和那是OP试图避免做的事情。
if_this_would_compile(...) 构造 - 并且(b)我认为在某些情况下它为设计带来的增加的干燥度可能超过增加的“远距离动作”复杂性。 (许多类似但不太通用的构造,例如模板 arg 推导 + SFINAE,在 Boost 等备受推崇的库中已经违反了您的 (2)。)
你不能在这里对 foo 使用完全专业化(或重载)吗?通过说有函数模板调用 bar 但对于某些类型完全专门化它来调用 baz?
【讨论】: