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

Jes*_*der 34 c++ templates sfinae

我想做点什么

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

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

Joh*_*itb 33

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

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

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

bar如果没有其他内容匹配,则将调用该函数,因为省略号的转换成本最低.现在,通过using指令将候选者包含到您的函数中fallback,以便将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);
}
Run Code Online (Sandbox Code Playgroud)

如果选择了我们的函数,则逗号运算符调用将返回对它的引用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);
}
Run Code Online (Sandbox Code Playgroud)

如果现有函数也有省略号,则此解决方案不明确.但这似乎不太可能.使用后备测试:

struct C { };
int main() {
  // => "not available, calling baz..."
  foo(C());
}
Run Code Online (Sandbox Code Playgroud)

并且如果找到使用参数依赖查找的候选者

struct C { };
void bar(C) {
  std::cout << "called!";
}
int main() {
  // => "called!"
  foo(C());
}
Run Code Online (Sandbox Code Playgroud)

在定义范围内测试不合格的查找,让我们定义和以下的功能foo_implfoo(把foo_impl模板上面foo,所以它们均具有相同的定义范围内)

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

// ... foo template ...

int main() {
  // => "bar(double) called!"
  foo(12);
}
Run Code Online (Sandbox Code Playgroud)

  • 哇靠!O_o你在哪里提出了使用逗号运算符的想法?(弄清楚你在那里花了15分钟做了什么......) (5认同)
  • @Faisal Vali,谢谢.我没有只用一个'op,'调用来做,因为有些类型会重载模板化的"op",并且接受标志作为第二个参数(就像`del << a,b;`,它有一个` delim operator,(delim,T const&);`).所以做`(bar(t),flag())`会产生歧义.我们也无法交换订单​​,因为`bar(t)`可能有类型`void`.有一个'op,'接受标志作为第一个参数当然不太可能 - 因此那三方面的东西:) (3认同)
  • 嗯 - 好点 - 但这里有一个替代方案 - 不确定是否每个人都会同意它更简单但输入更少;)而不是添加两个额外的逗号重载来劫持ADL,我们可以尝试通过添加块声明来关闭ADL .所以在'using namespace fallback;'之后添加以下内容:使用fallback :: operator,; void operator,(flag,int); 然后你可以做尺寸(bar(t),flag())而不受肆无忌惮的感觉.块声明阻止ADL(隐藏提升或全局操作),永远不会被调用,不隐藏正确的',op'也不隐藏内置',op' - 你的想法? (2认同)

sbi*_*sbi 6

litb给了你一个很好的答案.但是,我想知道,考虑到更多的背景,我们是否能够提出一些不那么通用的东西,而且还会减少,嗯,精心设计

例如,什么类型可以T?什么?几种类型?你可以控制的一套非常有限的套装?你设计的一些课程与功能一起foo?鉴于后者,你可以简单地说出类似的东西

typedef boolean<true> has_bar_func;
Run Code Online (Sandbox Code Playgroud)

进入类型,然后切换到不同的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() );
}
Run Code Online (Sandbox Code Playgroud)

此外,bar/ baz函数可以有任何签名,是否有一个有限的限制集,或只有一个有效的签名?如果是后者,litb(优秀)后备理念,结合使用元功能sizeof可能会更简单一些.但是我没有探索过,所以这只是一个想法.

  • +1,我喜欢这种干净简单的方法.我是否可以建议创建一个单独的traits类,例如foo_traits <T>,用于包含has_bar_func?这样你也可以处理基本类型. (2认同)