【问题标题】:Why is SFINAE causing failure when there are two functions with different signatures?当有两个具有不同签名的函数时,为什么 SFINAE 会导致失败?
【发布时间】:2015-04-21 12:44:58
【问题描述】:

我试图围绕这个问题 here 回想一下,因为它的编写方式隐藏了它实际在做什么。所以我把它改写成这样:

template<typename CLASS>
struct has_begin
{
    // NOTE: sig_matches() must come before fn_exists() as it is used for its
    //       type.  Also, no function bodies are needed as they are never called.
    
    // This matching sig results in a return type of true_type
    template<typename A_CLASS>
    static auto
        sig_matches(void(A_CLASS::*)())
        -> std::true_type;
    
    // If the member function A_CLASS::begin exists and a sig_matches() function
    // exists with the required sig, then the return type is the return type of
    // sig_matches(), otherwise this function can't exist because at least one
    // the types don't exist so match against fn_exists(...).
    template <typename A_CLASS>
    static auto
        fn_exists(decltype(&A_CLASS::begin))          
        -> decltype(sig_matches<A_CLASS>(&A_CLASS::begin));

    // Member function either doesn't exist or doesn't match against a 
    // sig_matches() function.
    template<typename A_CLASS>
    static auto
        fn_exists(...)
        -> std::false_type;

    // Intermediate storage of type for clarity
    typedef decltype(fn_exists<CLASS>(nullptr)) type;

    // Storing the resulting value
    static int const value = type::value;
};

这样做之后,发生的事情就相当容易了。然而,我发现了一些奇怪的东西。如果一个类通过 2 个开始签名传递给它,其中一个与 has_begin::sig_matches() 匹配,它将无法与它匹配。

#include <iostream>
#include <type_traits>
struct A
{
    void begin()
    {
        std::cout << "begin() called 1" << std::endl;
    }
};

struct B {};

struct C
{
    void begin()
    {
        std::cout << "begin() called 1" << std::endl;
    }

    void begin(float)
    {
        std::cout << "begin() called 2" << std::endl;
    }
};

template<typename T, typename...ARGs>
typename std::enable_if<!!has_begin<T>::value>::type
    call(ARGs...args)
{
    std::cout << "Found(" << has_begin<T>::value << ")" << std::endl;
    T().begin(args...);
}

template<typename T, typename...ARGs>
typename std::enable_if<!has_begin<T>::value>::type
    call(ARGs...)
{
    std::cout << "NOT Found(" << has_begin<T>::value << ")" << std::endl;
}

int main()
{
    call<A>(); // A::begin() called
    call<B>(); // B has no begin()
    call<C>(); // C::begin() is not called.
    return 0;
}

demo

为什么与C::begin() 匹配失败?

编辑

原因是&amp;A_CLASS::begin 不明确。修正后的类如下:

template<typename CLASS>
struct has_begin
{
    // NOTE: No function bodies are needed as they are never called.
    
    // If the member function A_CLASS::begin exists with the required sig,
    // then the return type is true_type otherwise this function can't
    // exist because the type cannot be deduced.
    template <typename A_CLASS>
    static auto
        fn_exists(decltype((void(A_CLASS::*)())&A_CLASS::begin))          
        -> std::true_type;

    // Member function either doesn't exist or doesn't match against the
    // required signature
    template<typename A_CLASS>
    static auto
        fn_exists(...)
        -> std::false_type;

    // Intermediate storage of type for clarity
    typedef decltype(fn_exists<CLASS>(nullptr)) type;

    // Storing the resulting value
    static int const value = type::value;
};

Yakk 和 dyp 提出了一个很好的观点。这是一种执行相同操作但具有 compatible 签名的方法:

template<typename CLASS>
struct has_begin
{
    // NOTE: No function bodies are needed as they are never called.

    // If the member function A_CLASS::begin exists that has a compatible sig, 
    // then the return type is true_type otherwise this function can't exist
    // because the type cannot be deduced.
    template <typename A_CLASS>
    static auto
        fn_exists(decltype(std::declval<A_CLASS>().begin())*)          
        -> std::true_type;

    // Member function either doesn't exist or doesn't match against the
    // required compatible signature
    template<typename A_CLASS>
    static auto
        fn_exists(...)
        -> std::false_type;

    // Intermediate storage of type for clarity
    typedef decltype(fn_exists<CLASS>(nullptr)) type;

    // Storing the resulting value
    static int const value = type::value;
};

我发现这比 Yakks 的回答更干净,因为它不需要详细的命名空间和其他“噪音”,而是 YYMV。

【问题讨论】:

  • decltype(&amp;A_CLASS::begin) 仅在 &amp;A_CLASS::begin 引用单个函数时是格式正确的。在C 的情况下,它指的是一个重载集,它没有单一类型。见[over.over]/1
  • 通常更容易检查函数调用表达式是否格式正确,而不是检查是否可以形成指向该函数的指针。例如,template&lt;typename A_CLASS&gt; static auto fn_exists(std::nullptr_t) -&gt; decltype(std::declval&lt;A_CLASS&amp;&gt;().begin(), void(), std::true_type{}); 这可以简化,参见例如stackoverflow.com/a/26533335
  • 如果替换后存在歧义重载,则无法编译。
  • 派对有点晚了@DarioOO。 ;)
  • 我懒得提点 =) 我更喜欢简洁的 cmets

标签: c++ c++11 sfinae


【解决方案1】:

这是 C++11。别再做那种 C++03 体操了。

// bundle of types:
template<class...>struct types{using type=types;};

// comes in std in C++14 or 1z, but easy to write here:
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;

// hide the SFINAE stuff in a details namespace:
namespace details {
  template<template<class...>class Z, class types, class=void>
  struct can_apply : std::false_type {};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z,types<Ts...>,void_t<
    Z<Ts...>
  >>:std::true_type{};
}
// can_apply<template, types...> is true
// iff template<types...> is valid:
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,types<Ts...>>;

以上是一次写入样板。它为您提供了一个 can_apply&lt;template, Ts...&gt; 帮助器,使编写“我们有方法吗”和其他类似的测试变得容易:

// the type of X.begin(), in a SFINAE friendly manner:
template<class X>
using begin_result = decltype( std::declval<X>().begin() );

// Turn the above into a "is there a begin" test:
template<class X>
using has_begin = can_apply< begin_result, X >;

现在has_begin&lt;X&gt;{} 为真当且仅当 X 可以用.begin() 调用。

这也修复了代码中的一个缺陷。

struct foo {
  void begin(double x = 0.0) {}
};

你的测试会失败,但会通过我的测试。

【讨论】:

  • 看起来你做的体操比我多。我觉得这很难读,但我会更多地研究它。
  • @Adrian 我清理了一些乱七八糟的东西。现在写can_apply有点乱,但是一旦完成(并且可以在实用程序头中完成一次),写begin_resulthas_begin各1-2行。
  • 与我写的相比,这有什么好处?
  • 另外,我实际上需要精确而不仅仅是兼容的签名。你能考虑到这一点来编辑这个吗?
  • @Adrian Yours 检查特定符号集是否生成指向具有特定签名的方法的指针。我的检查你是否可以做一些看起来像调用具有特定签名的方法的事情。如果您确实需要该特定语法来生成特定方法指针,那么您的方法是最好的:对我来说,这似乎是一个奇怪的极端情况。如果您需要代码能够调用具有特定参数集的特定方法,那么我的方法是最好的,因为我正在测试将要使用的内容,而不是通常与将要使用的内容兼容的内容。跨度>
【解决方案2】:

替换

// If the member function A_CLASS::begin exists and a sig_matches() function
// exists with the required sig, then the return type is the return type of
// sig_matches(), otherwise this function can't exist because at least one
// the types don't exist so match against fn_exists(...).
template <typename A_CLASS>
static auto
    fn_exists(decltype(&A_CLASS::begin))          
    -> decltype(sig_matches<A_CLASS>(&A_CLASS::begin));

// If the member function A_CLASS::begin exists and a sig_matches() function
// exists with the required sig, then the return type is the return type of
// sig_matches(), otherwise this function can't exist because at least one
// the types don't exist so match against fn_exists(...).
template <typename A_CLASS>
static auto
    fn_exists(std::nullptr_t)          
    -> decltype(sig_matches<A_CLASS>(&A_CLASS::begin));

作为

decltype(&A_CLASS::begin) is ambiguous when there are overloads for `begin`.

Live demo

【讨论】:

  • 是的,刚刚想通了。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-16
  • 2011-07-19
相关资源
最近更新 更多