Why is SFINAE causing failure when there are two functions with different signatures?

▼魔方 西西 提交于 2019-12-04 21:01:09

Replace

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

By

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

As

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

Live demo

This is C++11. Stop doing that C++03 gymnastics.

// 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...>>;

the above is write-once boilerplate. It gives you a can_apply<template, Ts...> helper that makes writing "do we have a method" and other similar tests easy:

// 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 >;

and now has_begin<X>{} is true if and only if X can be called with .begin().

This also fixes a flaw in your code.

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

will fail your test, but pass mine.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!