0

I was trying to wrap my head around this question here because it was written in such a way that it was hiding what it was actually doing. So I rewrote it as such:

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

After doing that, it was fairly easy what was happening. However, I found something odd. If a class was passed to this with 2 begin signatures, one of which matched against has_begin::sig_matches(), it would fail to match against it.

#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

Why is it failing to match against C::begin()?

Edit

The reason is that &A_CLASS::begin is ambiguous. The corrected class is as follows:

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 and dyp brought up a good point. Here is a way to do the same but with a compatible signature:

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

I find this cleaner than Yakks answer as it doesn't require detail namespaces and other 'noise', but YYMV.

Community
  • 1
  • 1
Adrian
  • 10,246
  • 4
  • 44
  • 110
  • 4
    `decltype(&A_CLASS::begin)` is only well-formed if `&A_CLASS::begin` refers to a single function. In the case of `C`, it refers to an overload set, which does not have a single type. See [over.over]/1 – dyp Apr 21 '15 at 12:50
  • 4
    It is often easier to check if a function-call expression is well-formed, instead of checking if you can form a pointer to that function. For example, `template static auto fn_exists(std::nullptr_t) -> decltype(std::declval().begin(), void(), std::true_type{});` This can be simplified, see e.g. http://stackoverflow.com/a/26533335/ – dyp Apr 21 '15 at 13:04
  • If after substitution there are ambiguos overloads it can't compile. – CoffeDeveloper Apr 21 '15 at 13:35
  • 1
    A little late to the party @DarioOO. ;) – Adrian Apr 21 '15 at 13:37
  • I don' bother making points =) I prefer concise comments – CoffeDeveloper Apr 21 '15 at 14:28
  • `decltype(...)*` I chose not to use this in my suggestion since it is possible that `decltype(...)` yields a reference type. (Unlikely for this particular function, though.) – dyp Apr 21 '15 at 16:26
  • Please don't add *solutions* to your question. If you have an answer to your own question, feel free to add it *as an answer*. – dyp Apr 21 '15 at 16:27

2 Answers2

5

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    Looks like you are doing a lot more gymnastics than I. I find this very difficult to read, but I will study it more. – Adrian Apr 21 '15 at 13:44
  • @Adrian I cleaned up some of the mess. Now there is a bit of mess to write `can_apply`, but once that is done (and it can be done once in a utility header), writing `begin_result` and `has_begin` is 1-2 lines each. – Yakk - Adam Nevraumont Apr 21 '15 at 14:05
  • What benefits does this have over the ones I wrote? – Adrian Apr 21 '15 at 14:21
  • Also, I actually need exact and not just compatible signatures. Can you edit this with that in mind? – Adrian Apr 21 '15 at 14:38
  • @Adrian Yours checks that a particular set of symbols generates a pointer to a method with a particular signature. Mine checks that you can do something that looks like calling a method with a particular signature. If you actually need that particular syntax to produce a particular method pointer, then your method is best: that, to me, seems like a strange corner case. If you need code to be able to call a particular method with a particular set of parameters, then my method is best, because I'm testing what will be used, instead of something that is usually compatible with what will be used. – Yakk - Adam Nevraumont Apr 21 '15 at 14:40
  • In particular, note the `foo` which fails your test, passes mine, and quite clearly can be `f.begin()`'d. – Yakk - Adam Nevraumont Apr 21 '15 at 14:41
  • No, my last example checks if a function exists by seeing if (as you say) *"you can do something that looks like calling a method with a particular signature"* and then takes the return type and makes it into a pointer. It will work with your *"can be `f.begin()`'d"* test. The 2nd last one is for an exact function signature. Are you saying that you are doing something that will do something in between? – Adrian Apr 21 '15 at 14:54
  • @Adrian I'm sorry, I don't go up and reread your question when you make a comment down here. At the time I wrote this answer, you had 2 solutions in your question: since then you have added more. Your `has_begin` requires writing all that boilerplate for every method you want to test. I count 11 lines in your example per method you want to test? Vs 4 in mine? And your `has_begin` lacks a `constexpr operator bool`, a `constexpr bool operator()` and cannot be tag dispatched to `std::true_type`, etc etc: `std::true_type` is more than a type with a `::value` that is `true`. – Yakk - Adam Nevraumont Apr 21 '15 at 15:19
3

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

Jarod42
  • 203,559
  • 14
  • 181
  • 302