34

我想做类似的事情

template <typename T>
void foo(const T& t) {
   IF bar(t) would compile
      bar(t);
   ELSE
      baz(t);
}

我认为使用enable_if的东西可以在这里完成工作,foo分成两部分,但我似乎无法弄清楚细节。实现这一目标的最简单方法是什么?

4

7 回答 7

32

对 name 进行了两次查找bar。一种是在 的定义上下文中进行不合格的查找foo。另一种是在每个实例化上下文中的参数相关查找(但每个实例化上下文的查找结果不允许改变两个不同实例化上下文之间的行为)。

fallback要获得所需的行为,您可以在返回某些唯一类型的命名空间中定义一个回退函数

namespace fallback {
  // sizeof > 1
  struct flag { char c[2]; };
  flag bar(...);
}

bar如果没有其他匹配项,将调用该函数,因为省略号具有最差的转换成本。现在,通过 的 using 指令将该候选者包含到您的函数中fallback,以便将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 的类型,如果选择了其他内容,则返回 sizeof 大于 1 的类型(将使用内置逗号运算符,因为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);
}
于 2009-09-06T18:49:40.203 回答
6

litb 给了你一个很好的答案。但是,我想知道,考虑到更多的上下文,我们是否不能想出一些不那么通用但也更少,嗯,详细的东西?

例如,可以是哪些类型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可能会更简单一些。但是这个我没有探索过,所以只是一个想法。

于 2009-09-06T19:53:44.710 回答
3

我认为 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)

于 2009-09-07T10:48:58.820 回答
2

编辑:我说得太早了!litb 的回答显示了如何实际做到这一点(可能会以你的理智为代价...... :-P)

不幸的是,我认为检查“是否会编译”的一般情况超出了函数模板参数推导 + 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()的模板本身就是一个带有 type 模板参数的函数模板T,则会出现歧义,因为两个模板都不比另一个模板更专业,编译器会抱怨。
  • 隐式转换也不起作用,并且会导致难以诊断的问题:假设有一个预先存在的bar(long)foo(123)被调用的。在这种情况下,编译器将悄悄地选择实例化“备份”bar()模板T = int而不是执行int->long提升,即使后者本来可以编译并正常工作!

简而言之:没有简单、完整的解决方案,而且我很确定甚至没有一个棘手的、完整的解决方案。:(

于 2009-09-06T18:23:29.837 回答
2

如果您愿意将自己限制在 Visual C++,您可以使用__if_exists__if_not_exists语句。

在紧要关头很方便,但特定于平台。

于 2009-09-06T19:13:36.567 回答
2
//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);
    }
于 2009-09-06T21:01:17.593 回答
0

您是否无法在 foo 上使用完全专业化(或重载)。通过说有函数模板调用 bar 但对于某些类型完全专门化它来调用 baz?

于 2009-09-06T17:32:03.200 回答