繁体   English   中英

具有默认参数的函数的C ++部分排序

[英]C++ partial ordering for a function with default argument

请考虑以下代码:

template <class...>
using void_t = void;

template <class T>
void bar(T){}

template <class T>
void bar(T, void_t<decltype(std::declval<T>().foo())>* = 0) {}

struct A { void foo(); } a;
bar(a); // gives a compiler error on ambiguous call

所以问题是,为什么这些过载模糊不清? 为什么第二次重载不被认为是更严格的,比编译器更专业的第二次?

您正在尝试使用SFINAE在特定情况下强制选择特定候选项(此处,可调用实体foo存在在T值中不带任何参数)。 这里到底发生了什么?

具有void_t的模板已为您的struct A定义良好,因此在调用时您有两个有效的重载void_t候选者。 如果您想使用SFINAE,您必须确保对于任何给定的呼叫,只有一个过载可用。 为此,您应该首先将测试嵌入到类型特征中。 为此你可以在Yakk的can_apply工具举例 ,我在这里无耻地复制,因为它非常适合你的需要:

namespace details {
  // if Z<Ts...> is invalid, false_type:
  template <template<class...> class Z, class always_void, class... Ts>
  struct can_apply : std::false_type {};

  // if Z<Ts...> is valid, true_type:
  template <template<class...> class Z, class... Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...> : std::true_type {};
}
// alias to inject the void type where we need it for SFINAE:
template <template<class...> class Z, class... Ts>
using can_apply = details::can_apply<Z, void, Ts...>;

template <typename T>
using has_foo_t = decltype(std::declval<T>().foo());

template <typename T>
using has_foo = can_apply<has_foo_t, T>;

现在我们只需要在模板定义中使用上面定义的特征:

// The enable_if with the negation is needed to invalidate
// this implementation when T indeed has foo().
// This is what you were missing in your original idea.
template <typename T>
std::enable_if_t<!has_foo<T>::value> bar(T) {
    std::cout << "T has no foo(void)" << std::endl;
}

template <typename T>
std::enable_if_t<has_foo<T>::value> bar(T) {
    std::cout << "T has a foo(void)" << std::endl;
}

您可以在Coliru上看到一个正在运行的示例。

通常的方法之一是创建一个同样专用模板的分离。

#include <utility>
#include <iostream>

template<class T>
struct has_foo_impl
{
  template<class U> static auto test(U* p) -> decltype(p->foo(), void(), std::true_type()); 
  static auto test(...) -> decltype(std::false_type()); 

  using type = decltype(test((T*)nullptr));
};

template<class T> using has_foo = typename has_foo_impl<T>::type;

template <class...>
using void_t = void;

template <class T, std::enable_if_t<not has_foo<T>::value>* = nullptr>
void bar(T){
  std::cout << "not me\n";
}

template <class T, std::enable_if_t<has_foo<T>::value>* = nullptr>
void bar(T) {
  std::cout << "me\n";
}

struct A { void foo(); } a;

int main()
{
    bar(a); // "me"
}

没有一个功能比另一个更专业

无论如何,您可以通过使用两个重载函数来强制选择,如下所示:

#include<utility>

template <class T>
void bar(char, T){}

template <class T>
auto bar(int, T) -> decltype(std::declval<T>().foo(), void()) {}

struct A { void foo(); } a;

int main() {
    bar(0, a);
}

对于0int ,首先尝试bar(int, T) 由于sfinae,如果T没有foo成员函数,它可以被丢弃。 无论如何, 0可以转换为char ,因此在这种情况下选择bar(char, T)
如果碰巧两个函数都是可行的解决方案,则调用bar(int, T) ,因为它不需要任何隐式转换,并且调用不再是歧义。

这是用g ++编译的其他解决方案:

//g++  4.9.3

#include <iostream>

template <class T>
    void bar(T)
{std::cout << "not foo boo...\n";}

template <class T >
    void bar( decltype(std::declval<T>().foo(),T())) 
{std::cout << "foo\n";}

template <class T >
    void bar( decltype(std::declval<T>().boo(),T())) 
{std::cout << "boo\n";}

struct Foo { void foo(); } f;

struct Boo {void boo(); } b;

struct NotFooBoo { } n;
template <class T>
    auto func(T&& t)
{
    return bar<typename std::decay<T>::type>(std::forward<T>(t));
}
int main()
{
    func(f);
    func(b);
    func(n);
}

因为您使用重载函数的默认值,所以编译器不知道您要使用哪个函数。

void bar(T, void_t<decltype(std::declval<T>().foo())>* = 0)

= 0 

它工作正常。

/编辑

template <class...>
using void_t = void;

template <class T>
void bar(T)
{}

template <class T>
void bar(T, void_t<decltype(std::declval<T>().foo())>*)
{}

struct A
{
    void foo();
} a;

int main()
{
    bar(a);
    return 0;
}

有用。

The program '[7464] Project3.exe' has exited with code 0 (0x0).

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM