如果它存在,如何调用模板化函数,否则调用其他函数?

时间:2009-09-06 17:23:17

标签: c++ templates sfinae

我想做点什么

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

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

7 个答案:

答案 0 :(得分:33)

对名称bar进行了两次查找。一个是foo定义上下文中的非限定查找。另一个是在每个实例化上下文中依赖于参数的查找(但是不允许在每个实例化上下文中查找的结果改变两个不同实例化上下文之间的行为)。

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

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

如果没有其他内容匹配,则会调用bar函数,因为省略号的转换成本最低。现在,通过fallback的using指令将候选人包含到您的函数中,以便将fallback::bar作为候选者包含在bar的调用中。

现在,要查看对bar的调用是否解析为您的函数,您将调用它,并检查返回类型是否为flag。否则选择的函数的返回类型可能是无效的,所以你必须做一些逗号操作符技巧来解决这个问题。

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_implfoo之上定义以下函数(将foo_impl模板放在foo之上,因此它们具有相同的定义上下文)< / p>

void bar(double d) {
  std::cout << "bar(double) called!";
}

// ... foo template ...

int main() {
  // => "bar(double) called!"
  foo(12);
}

答案 1 :(得分:6)

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的元函数可能会更简单一些。但是我没有探索过,所以这只是一个想法。

答案 2 :(得分: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)

上失败

答案 3 :(得分:2)

编辑:我说得太快了! 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)。在这种情况下,编译器将悄悄地选择使用bar()实例化“备份”T = int模板,而不是执行int->long促销,即使后者已经编译并且工作正常! / LI>

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

答案 4 :(得分:2)

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

方便处理,但特定于平台。

答案 5 :(得分: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);
    }

答案 6 :(得分:0)

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