当有两个具有不同签名的函数时,为什么 SFINAE 会导致失败?
Why is SFINAE causing failure when there are two functions with different signatures?
我试图绕过这个问题 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;
};
这样做之后,发生的事情就很容易了。然而,我发现了一些奇怪的事情。如果一个 class 被传递给这个带有 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;
}
为什么无法匹配 C::begin()
?
编辑
原因是&A_CLASS::begin
有歧义。修正后的class如下:
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 的答案更清晰,因为它不需要详细的命名空间和其他 'noise',而是 YYMV。
替换
// 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`.
这是 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<template, Ts...>
帮助程序,使编写 "do we have a method" 和其他类似测试变得容易:
// 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<X>{}
为真当且仅当 X 可以用 .begin()
调用。
这也修复了您代码中的一个缺陷。
struct foo {
void begin(double x = 0.0) {}
};
你的测试会失败,但我的会通过。
我试图绕过这个问题 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;
};
这样做之后,发生的事情就很容易了。然而,我发现了一些奇怪的事情。如果一个 class 被传递给这个带有 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;
}
为什么无法匹配 C::begin()
?
编辑
原因是&A_CLASS::begin
有歧义。修正后的class如下:
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 的答案更清晰,因为它不需要详细的命名空间和其他 'noise',而是 YYMV。
替换
// 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`.
这是 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<template, Ts...>
帮助程序,使编写 "do we have a method" 和其他类似测试变得容易:
// 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<X>{}
为真当且仅当 X 可以用 .begin()
调用。
这也修复了您代码中的一个缺陷。
struct foo {
void begin(double x = 0.0) {}
};
你的测试会失败,但我的会通过。