6

When writing template specialization with SFINAE you often come to the point where you need to write a whole new specialization because of one small not-existing member or function. I would like to pack this selection into a small statement like orElse<T a,T b>.

small example:

template<typename T> int get(T& v){
    return orElse<v.get(),0>();
}

is this possible?

Telokis
  • 3,399
  • 14
  • 36
tly
  • 1,202
  • 14
  • 17

2 Answers2

2

Yes, this is more or less possible. It is known as a "member detector". See this wikibooks link for how to accomplish this with macros. The actual implementation will depend on whether you are using pre- or post-C++11 and which compiler you are using.

  • Also, your post is a possible duplicate of: http://stackoverflow.com/questions/257288/is-it-possible-to-write-a-c-template-to-check-for-a-functions-existence/9154394 – Jonathan Hopkins Mar 07 '15 at 01:54
  • "expression compiles" and "member function exists" aren't really the same at all – sp2danny Mar 07 '15 at 11:10
  • this member detector is a good start. i just thought that a universal solution could be possible. – tly Mar 07 '15 at 11:32
  • @sp2danny ah yes, my mistake. I guess the question is why would the expression not compile correctly then? Could the problem be reduced to member detection? ie, code that evaluates if particular member(s) exists then evaluate expression, otherwise some default expression. I think I would also benefit from tly expanding on the question. – Jonathan Hopkins Mar 08 '15 at 06:41
2

The intent of orElse<v.get(),0>() is clear enough, but if such a thing could exist, it would have to be be one of:

Invocation Lineup

orElse(v,&V::get,0)
orElse<V,&V::get>(v,0)
orElse<V,&V::get,0>(v)

where v is of type V, and the function template thus instantiated would be respectively:

Function Template Lineup

template<typename T>
int orElse(T & obj, int(T::pmf*)(), int deflt);

template<typename T, int(T::*)()>
int orElse(T & obj, int deflt);

template<typename T, int(T::*)(), int Default>
int orElse(T & obj);

As you appreciate, no such a thing can exist with the effect that you want.

For any anyone who doesn't get that, the reason is simply this: None of the function invocations in the Invocation Lineup will compile if there is no such member as V::get. There's no getting round that, and the fact that the function invoked might be an instantiation of a function template in the Function Template Lineup makes no difference whatever. If V::get does not exist, then any code that mentions it will not compile.

However, you seem to have a practical goal that need not be approached in just this hopeless way. It looks as if, for a given name foo and an given type R, you want to be able to write just one function template:

template<typename T, typename ...Args>
R foo(T && obj, Args &&... args);

which will return the value of R(T::foo), called upon obj with arguments args..., if such a member function exists, and otherwise return some default R.

If that's right, it can be achieved as per the following illustration:

#include <utility>
#include <type_traits>

namespace detail {

template<typename T>

T default_ctor()
{
    return T();
}

// SFINAE `R(T::get)` exists
template<typename T, typename R, R(Default)(), typename ...Args>
auto get_or_default(
    T && obj,
    Args &&... args) ->
    std::enable_if_t<
        std::is_same<R,decltype(obj.get(std::forward<Args>(args)...))
    >::value,R>
{
    return obj.get(std::forward<Args>(args)...);
}

// SFINAE `R(T::get)` does not exist
template<typename T, typename R, R(Default)(), typename ...Args>
R get_or_default(...)
{
    return Default();
}

} //namespace detail


// This is your universal `int get(T,Args...)`
template<typename T, typename ...Args>
int get(T && obj, Args &&... args)
{
    return detail::get_or_default<T&,int,detail::default_ctor>
        (obj,std::forward<Args>(args)...);
}

// C++14, trivially adaptable for C++11

which can be tried out with:

#include <iostream>

using namespace std;

struct A
{
    A(){};
    int get() {
        return 1;
    }
    int get(int i) const  {
        return i + i;
    }
};

struct B
{
    double get() {
        return 2.2;
    }
    double get(double d) {
        return d * d;
    }
};

struct C{};

int main()
{
    A const aconst;
    A a;
    B b;
    C c;
    cout << get(aconst) << endl;    // expect 0
    cout << get(a) << endl;         // expect 1 
    cout << get(b) << endl;         // expect 0
    cout << get(c) << endl;         // expect 0
    cout << get(a,1) << endl;       // expect 2
    cout << get(b,2,2) << endl;     // expect 0
    cout << get(c,3) << endl;       // expect 0
    cout << get(A(),2) << endl;     // expect 4
    cout << get(B(),2,2) << endl;   // expect 0
    cout << get(C(),3) << endl;     // expect 0
    return 0;
}

There is "compound SFINAE" in play in the complicated return type:

std::enable_if_t<
        std::is_same<R,decltype(obj.get(std::forward<Args>(args)...))
    >::value,R>

If T::get does not exist then decltype(obj.get(std::forward<Args>(args)...) does not compile. But if it does compile, and the return-type of T::get is something other than R, then the std::enable_if_t type specifier does not compile. Only if the member function exists and has the desired return type R can the R(T::get) exists case be instantiated. Otherwise the catch-all R(T::get) does not exist case is chosen.

Notice that get(aconst) returns 0 and not 1. That's as it should be, because the non-const overload A::get() cannot be called on a const A.

You can use the same pattern for any other R foo(V & v,Args...) and existent or non-existent R(V::foo)(Args...). If R is not default-constructible, or if you want the default R that is returned when R(V::foo) does not exist to be something different from R(), then define a function detail::fallback (or whatever) that returns the desired default R and specify it instead of detail::default_ctor

How nice it would be it you could further template-paramaterize the pattern to accomodate any possible member function of T with any possible return type R. But the additional template parameter you would need for that would be R(T::*)(typename...),and its instantiating value would have to be &V::get (or whatever), and then the pattern would force you into the fatal snare of mentioning the thing whose existence is in doubt.

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182