13

I have the following snipped of code, which does not compile.

#include <iostream>

struct A {
    void foo() {}
};

struct B : public A {
    using A::foo;
};

template<typename U, U> struct helper{};

int main() {
    helper<void (A::*)(), &A::foo> compiles;
    helper<void (B::*)(), &B::foo> does_not_compile;

    return 0;
}

It does not compile since &B::foo resolves to &A::foo, and thus it cannot match the proposed type void (B::*)(). Since this is part of a SFINAE template that I am using to check for a very specific interface (I'm forcing specific argument types and output types), I would like for this to work independently of inheritances, while keeping the check readable.

What I tried includes:

  • Casting the second part of the argument:

    helper<void (B::*)(), (void (B::*)())&B::foo> does_not_compile;

    This unfortunately does not help as the second part is now not recognized as a constant expression, and fails.

  • I've tried assigning the reference to a variable, in order to check that.

    constexpr void (B::* p)() = &B::foo; helper<void (B::* const)(), p> half_compiles;

    This code is accepted by clang 3.4, but g++ 4.8.1 rejects it, and I have no idea on who's right.

Any ideas?

EDIT: Since many comments are asking for a more specific version of the problem, I'll write it here:

What I'm looking for is a way to explicitly check that a class respects a specific interface. This check will be used to verify input arguments in templated functions, so that they respect the contract that those functions require, so that compilation stops beforehand in case the class and a function are not compatible (i.e. type traits kind of checking).

Thus, I need to be able to verify return type, argument type and number, constness and so on of each member function that I request. The initial question was the checking part of the bigger template that I'm using to verify matches.

Svalorzen
  • 5,353
  • 3
  • 30
  • 54
  • Could you provide more detail about what you're trying to achieve? There's likely an alternative way to do it. – jrok May 06 '14 at 16:43
  • As `decltype` was already mentioned, may I just suggest that you could write `helper compiles;` ? – Ivan Vergiliev May 06 '14 at 16:45
  • The final goal is the following: https://ideone.com/mxIVw3 (I can copy it here if you think it is worthwhile, I think it detracts). The idea, as I said, is to verify public interfaces as precisely as possible, while keeping readability. – Svalorzen May 06 '14 at 16:54
  • @IvanVergiliev The problem is that will match foo no matter what its actual signature is, while I want to verify that it matches what I need. – Svalorzen May 06 '14 at 16:55
  • @svalorzen ``? With the magic as an `=void` parameter (or whatever). – Yakk - Adam Nevraumont May 06 '14 at 17:06
  • 1
    @Svalorzen: What is your objective? If you simply want to check the existence of the interface on a given type `T`, then there're *better* ways to do it. – Nawaz May 06 '14 at 17:12
  • @Yakk Would you mind making a more concrete example? I'm not really *that* much an expert; though I can definitely work and build on an example =) – Svalorzen May 06 '14 at 17:13
  • @Nawaz I wouldn't know, really. What I'm doing I'm doing after browsing endlessly StackOverflow answers and blogs, and picking the bits that seemed the most clean. I'm definitely open to alternatives though, this is just the best I could come up with, that gives me the control I want. – Svalorzen May 06 '14 at 17:14
  • @Svalorzen describe your underlying problem. Do you want to test if a type `X` has a method `foo()` that can be called with zero arguments and returns something compatible with `R`? – Yakk - Adam Nevraumont May 06 '14 at 17:48
  • Although of no help to you: What you really want are concepts and concept based dispatching. – pmr May 08 '14 at 11:17

4 Answers4

6

A working solution to your problem as posted at https://ideone.com/mxIVw3 is given below - see also live example.

This problem is in a sense a follow-up of Deduce parent class of inherited method in C++. In my answer, I defined a type trait member_class that extracts a class from a given pointer to member function type. Below we use some more traits to analyse and then synthesize back such a type.

First, member_type extracts the signature, e.g. void (C::*)() gives void():

template <typename M> struct member_type_t { };
template <typename M> using  member_type = typename member_type_t <M>::type;

template <typename T, typename C>
struct member_type_t <T C::*> { using type = T;};

Then, member_class extracts the class, e.g. void (C::*)() gives C:

template<typename>
struct member_class_t;

template<typename M>
using member_class = typename member_class_t <M>::type;

template<typename R, typename C, typename... A>
struct member_class_t <R(C::*)(A...)> { using type = C; };

template<typename R, typename C, typename... A>
struct member_class_t <R(C::*)(A...) const> { using type = C const; };

// ...other qualifier specializations

Finally, member_ptr synthesizes a pointer to member function type given a class and a signature, e.g. C + void() give void (C::*)():

template <typename C, typename S>
struct member_ptr_t;

template <typename C, typename S>
using member_ptr = typename member_ptr_t <C, S>::type;

template <typename C, typename R, typename ...A>
struct member_ptr_t <C, R(A...)> { using type = R (C::*)(A...); };

template <typename C, typename R, typename ...A>
struct member_ptr_t <C const, R(A...)> { using type = R (C::*)(A...) const; };

// ...other qualifier specializations

The two previous traits need more specialization for different qualifiers to be more generic, e.g. const/volatile or ref-qualifiers. There are 12 combinations (or 13 including data members); a complete implementation is here.

The idea is that any qualifiers are transferred by member_class from the pointer-to-member-function type to the class itself. Then member_ptr transfers qualifiers from the class back to the pointer type. While qualifiers are on the class type, one is free to manipulate with standard traits, e.g. add or remove const, lvalue/rvalue references, etc.

Now, here is your is_foo test:

template <typename T>
struct is_foo {
private:
    template<
        typename Z,
        typename M = decltype(&Z::foo),
        typename C = typename std::decay<member_class<M>>::type,
        typename S = member_type<M>
    >
    using pattern = member_ptr<C const, void()>;

    template<typename U, U> struct helper{};

    template <typename Z> static auto test(Z z) -> decltype(
        helper<pattern<Z>, &Z::foo>(),
        // All other requirements follow..
        std::true_type()
    );

    template <typename> static auto test(...) -> std::false_type;

public:
    enum { value = std::is_same<decltype(test<T>(std::declval<T>())),std::true_type>::value };
};

Given type Z, alias template pattern gets the correct type M of the member pointer with decltype(&Z::foo), extracts its decay'ed class C and signature S, and synthesizes a new pointer-to-member-function type with class C const and signature void(), i.e. void (C::*)() const. This is exactly what you needed: it's the same with your original hard-coded pattern, with the type Z replaced by the correct class C (possibly a base class), as found by decltype.

Graphically:

M = void (Z::*)() const  ->   Z         +   void()
                         ->   Z const   +   void()
                         ->   void (Z::*)() const   ==   M
                         ->   SUCCESS

M = int (Z::*)() const&  ->   Z const&   +   int()
                         ->   Z const    +   void()
                         ->   void (Z::*)() const   !=   M
                         ->   FAILURE

In fact, signature S wasn't needed here, so neither was member_type. But I used it in the process, so I am including it here for completeness. It may be useful in more general cases.

Of course, all this won't work for multiple overloads, because decltype doesn't work in this case.

Community
  • 1
  • 1
iavr
  • 7,547
  • 1
  • 18
  • 53
  • I see what you are doing here. Basically you are forcing the templates to break everything down to their very component, and assembling the matches yourself. This is what I hoped to avoid though, since I've always found these walls of templates kind of impenetrable. Still, thank you very much for the effort! – Svalorzen May 06 '14 at 18:17
  • @Svalorzen If your purpose is to "verify public interfaces as precisely as possible", I think this is the most general solution. All traits defined here are clearly reusable, they are "library" components. You can use them pretty much as given in the provided link, so there's nothing to "penetrate". In fact, I have found such traits in implementations of `std::result_of`; they could be part of STL, but they are not, unfortunately. The only part relevant to your problem is alias `pattern`. – iavr May 06 '14 at 18:20
  • @Svalorzen By the way, there are no "matches" to assemble; only one type. The given type is decomposed into two parts, the parts can be manipulated/transformed, and then they are re-composed into a single type. Here we keep only one part (converted to `const`) and replace the other part by `void()`. – iavr May 06 '14 at 18:35
4

If you simply want to check the existence of the interface on a given type T, then there're better ways to do it. Here is one example:

template<typename T>
struct has_foo
{
    template<typename U>
    constexpr static auto sfinae(U *obj) -> decltype(obj->foo(), bool()) { return true; }

    constexpr static auto sfinae(...) -> bool { return false; }

    constexpr static bool value = sfinae(static_cast<T*>(0));
};

Test code:

struct A {
    void foo() {}
};

struct B : public A {
    using A::foo;
};

struct C{};

int main() 
{
    std::cout << has_foo<A>::value << std::endl;
    std::cout << has_foo<B>::value << std::endl;
    std::cout << has_foo<C>::value << std::endl;
    std::cout << has_foo<int>::value << std::endl;
    return 0;
}

Output (demo):

1
1
0
0

Hope that helps.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • The problem is that allows for pretty much any type of `foo` function. It does not check for constness nor virtual, it allows any return type, and it allows for conversions in the arguments (int to unsigned and similar). Which is why I was trying to avoid it. – Svalorzen May 06 '14 at 17:31
  • 1
    @Svalorzen: Alright. then see this: http://coliru.stacked-crooked.com/a/6f295c3345fb57ca ... Is this okay for you? Of course, it checks only the parameter type, not the return type (which can be done quite easily). It prints `0` (false) for `C` and `D` class types whose `foo` requires conversion! – Nawaz May 06 '14 at 17:47
  • This is a very neat trick! Could you make it also check for constness? – Svalorzen May 06 '14 at 18:15
  • @Svalorzen: `constness` of what? parameter? or function? – Nawaz May 06 '14 at 18:26
  • @Nawaz Of function, I assume. E.g. the member function pointer be of type `void (Z::*)() const`. – iavr May 06 '14 at 18:29
  • @Svalorzen: Note that `constness` of the function can be checked easily by using `obj` as `T * const`. But parameters' top-level constness cannot be checked, because the compiler ignore them when interpreting the signature of the function (i.e `void f(int);` and `void f(const int);` are exactly same declaration! – Nawaz May 06 '14 at 18:33
  • @Nawaz However using obj as `const` forces every required function to be `const`, while it may not necessarily be so (we may need a particular `foo() const` and `bar()` which is not required to be `const`). – Svalorzen May 06 '14 at 18:40
  • @Svalorzenat: Not clear enough. What do you want? Do you want to check whether `foo` is a const or not? Checking it for `const` doesn't make other function `bar` to be automatically const. – Nawaz May 06 '14 at 18:44
3

Here's a simple class that passes your tests (and doesn't require a dozen of specializations :) ). It also works when foo is overloaded. The signature that you wish to check can also be a template parameter (that's a good thing, right?).

#include <type_traits>

template <typename T>
struct is_foo {
    template<typename U>
    static auto check(int) ->
    decltype( static_cast< void (U::*)() const >(&U::foo), std::true_type() );
    //                     ^^^^^^^^^^^^^^^^^^^
    //                     the desired signature goes here

    template<typename>
    static std::false_type check(...);

    static constexpr bool value = decltype(check<T>(0))::value;
};

Live example here.

EDIT :

We have two overloads of check. Both can take a integer literal as a parameter and because the second one has an ellipsis in parameter list it'll never be the best viable in overload resolution when both overloads are viable (elipsis-conversion-sequence is worse than any other conversion sequence). This lets us unambiguously initialize the value member of the trait class later.

The second overload is only selected when the first one is discarded from overload set. That happens when template argument substitution fails and is not an error (SFINAE).

It's the funky expression on the left side of comma operator inside decltype that makes it happen. It can be ill-formed when

  1. the sub-expression &U::foo is ill-formed, which can happen when

    • U is not a class type, or
    • U::foo is inaccesible, or
    • there is no U::foo
  2. the resulting member pointer cannot be static_cast to the target type

Note that looking up &U::foo doesn't fail when U::foo itself would be ambiguous. This is guaranteed in certain context listed in C++ standard under 13.4 (Address of overloaded function, [over.over]). One such context is explicit type conversion (static_cast in this case).

The expression also makes use of the fact that T B::* is convertible to T D::* where D is a class derived from B (but not the other way around). This way there's no need for deducing the class type like in iavr's answer.

value member is then initialized with value of either true_type or false_type.


There's a potential problem with this solution, though. Consider:

struct X {
    void foo() const;
};

struct Y : X {
    int foo();   // hides X::foo
};

Now is_foo<Y>::value will give false, because name lookup for foo will stop when it encounters Y::foo. If that's not your desired behaviour, consider passing the class in which you wish to perform lookup as a template parameter of is_foo and use it in place of &U::foo.

Hope that helps.

Community
  • 1
  • 1
jrok
  • 54,456
  • 9
  • 109
  • 141
  • Wait, you, I, what. I need to play with this, I'm going to marry you! – Svalorzen May 08 '14 at 00:36
  • It works beautifully, and it is EXACTLY as I wanted it, and I cannot upvote it enough. Before accepting it, would you mind giving some insight in how you derived this? There's so little information out there on how to work on templates, it would be awesome to get some info on how to do this! – Svalorzen May 08 '14 at 01:01
  • @Svalorzen I'm afraid I'm already married. My wife doesn't mind editing SO answers, though, so there you go. :) Let me know if anything is unclear. – jrok May 08 '14 at 10:57
  • +1 Very neat. Plus, works for overloaded functions! I hope you can see at least that my dozen specializations are re-usable components--I had written them long before this question came up :-) – iavr May 08 '14 at 22:27
0

I suggest using decltype to generically determine the type of the member function pointers:

helper<decltype(&A::foo), &A::foo> compiles;
helper<decltype(&B::foo), &B::foo> also_compiles;

It may seem like a DRY violation, but repeating the name is fundamentally no worse than specifying the type separately from the name.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • 1
    The problem with this is that you are checking for the existence of a member named foo, but you cannot enforce any kind of common interface for the method. `A::foo` may be a `void()` while `B::foo` may be a `int(unsigned,double,float)`. This really cannot help if you are checking an interface because you want to use it in a templated algorithm. – Svalorzen May 06 '14 at 18:01
  • It only checks the member exists. It doesn't even check whether the member is function or data. `struct C { std::string foo; };` will (incorrectly) pass the test! – Nawaz May 06 '14 at 18:22
  • This use of `decltype` was my very first thought, and in fact was mentioned in a comment under the question by Ivan Vergiliev. But I think the question itself was not clear enough; I only understood the real problem when I saw the code at [https://ideone.com/mxIVw3](https://ideone.com/mxIVw3). – iavr May 06 '14 at 18:40
  • @Svalorzen This isn't checking for *anything*, per se - it's the simplest possible solution to the problem in the question: passing the type corresponding to a named member function pointer. If your real problem is not the one described in the question, you should ask another question or clarify this one. – Casey May 06 '14 at 18:50