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

发布于 2024-08-03 22:27:06 字数 264 浏览 6 评论 0原文

我想做一些事情,就像

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

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

I want to do something like

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

I thought that something using enable_if would do the job here, splitting up foo into two pieces, but I can't seem to work out the details. What's the simplest way of achieving this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

守望孤独 2024-08-10 22:27:06

针对名称 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 的类型,并且 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 之上,因此它们具有相同的定义上下文)

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

// ... foo template ...

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

There are two lookups that are done for the name bar. One is the unqualified lookup at the definition context of foo. The other is argument dependent lookup at each instantiation context (but the result of the lookup at each instantiation context is not allowed to change behavior between two different instantiation contexts).

To get the desired behavior, you could go and define a fallback function in a fallback namespace that returns some unique type

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

The bar function will be called if nothing else matches because the ellipsis has worst conversion cost. Now, include that candidates into your function by a using directive of fallback, so that fallback::bar is included as candidate into the call to bar.

Now, to see whether a call to bar resolves to your function, you will call it, and check whether the return type is flag. The return type of an otherwise chosen function could be void, so you have to do some comma operator tricks to get around that.

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);
}

If our function was selected then the comma operator invocation will return a reference to int. If not or if the selected function returned void, then the invocation returns void in turn. Then the next invocation with flag as second argument will return a type that has sizeof 1 if our fallback was selected, and a sizeof greater 1 (the built-in comma operator will be used because void is in the mix) if something else was selected.

We compare the sizeof and delegate to a struct.

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);
}

This solution is ambiguous if the existing function has an ellipsis too. But that seems to be rather unlikely. Test using the fallback:

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

And if a candidate is found using argument dependent lookup

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

To test unqualified lookup at definition context, let's define the following function above foo_impl and foo (put the foo_impl template above foo, so they have both the same definition context)

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

// ... foo template ...

int main() {
  // => "bar(double) called!"
  foo(12);
}
眼趣 2024-08-10 22:27:06

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

litb has given you a very good answer. However, I wonder whether, given more context, we couldn't come up with something that's less generic, but also less, um, elaborate?

For example, what types can be T? Anything? A few types? A very restricted set which you have control over? Some classes you design in conjunction with the function foo? Given the latter, you could simple put something like

typedef boolean<true> has_bar_func;

into the types and then switch to different foo overloads based on that:

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() );
}

Also, can the bar/baz function have just about any signature, is there a somewhat restricted set, or is there just one valid signature? If the latter, litb's (excellent) fallback idea, in conjunction with a meta-function employing sizeof might be a bit simpler. But this I haven't explored, so it's just a thought.

故人的歌 2024-08-10 22:27:06

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

I think litb's solution works, but is overly complex. The reason is that he's introducing a function fallback::bar(...) which acts as a "function of last resort", and then goes to great lengths NOT to call it. Why? It seems we have a perfect behavior for it:

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);
}

But as I indicated in a comment to litb's original post, there are many reasons why bar(t) could fail to compile, and I'm not certain this solution handles the same cases. It certainly will fail on a private bar::bar(T t)

掐死时间 2024-08-10 22:27:06

如果您愿意只使用 Visual C++,则可以使用 __if_exists__if_not_exists 声明。

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

If you're willing to limit yourself to Visual C++, you can use the __if_exists and __if_not_exists statements.

Handy in a pinch, but platform specific.

樱娆 2024-08-10 22:27:06

编辑:我说得太早了! litb的答案 显示了如何实际完成此操作(可能会损害您的理智......:-P)

不幸的是,我认为检查“是否可以编译”的一般情况超出了 函数模板参数推导+SFIN​​AE,这是这玩意的常用技巧。我认为您能做的最好的事情就是创建一个“备份”函数模板:

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()< 的函数或函数模板相比,它将被视为“不太专业” /code> 因此将在重载决策期间将优先级让给预先存在的函数或函数模板。除此之外:

  • 如果预先存在的 bar() 本身就是一个采用 T 类型的模板参数的函数模板,则会出现歧义,因为两个模板都不比其他的,编译器会抱怨。
  • 隐式转换也不起作用,并且会导致难以诊断的问题:假设有一个预先存在的 bar(long)foo(123) 是叫。在这种情况下,编译器会悄悄地选择用 T = int 实例化“备份”bar() 模板,而不是执行 int->long< /code> 促销,即使后者可以编译并正常工作!

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

EDIT: I spoke too soon! litb's answer shows how this can actually be done (at the possible cost of your sanity... :-P)

Unfortunately I think the general case of checking "would this compile" is out of reach of function template argument deduction + SFINAE, which is the usual trick for this stuff. I think the best you can do is to create a "backup" function template:

template <typename T>
void bar(T t) {   // "Backup" bar() template
    baz(t);
}

And then change foo() to simply:

template <typename T>
void foo(const T& t) {
    bar(t);
}

This will work for most cases. Because the bar() template's parameter type is T, it will be deemed "less specialised" when compared with any other function or function template named bar() and will therefore cede priority to that pre-existing function or function template during overload resolution. Except that:

  • If the pre-existing bar() is itself a function template taking a template parameter of type T, an ambiguity will arise because neither template is more specialised than the other, and the compiler will complain.
  • Implicit conversions also won't work, and will lead to hard-to-diagnose problems: Suppose there is a pre-existing bar(long) but foo(123) is called. In this case, the compiler will quietly choose to instantiate the "backup" bar() template with T = int instead of performing the int->long promotion, even though the latter would have compiled and worked fine!

In short: there's no easy, complete solution, and I'm pretty sure there's not even a tricky-as-hell, complete solution. :(

水染的天色ゝ 2024-08-10 22:27:06
//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);
    }
//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);
    }
握住我的手 2024-08-10 22:27:06

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

Are you not able to use full specialisation here (or overloading) on foo. By say having the function template call bar but for certain types fully specialise it to call baz?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文