6

I want to write a function which executes the method of some templated class, but should also compile fine if the class doesn't have it. In that case, it should just not call the function.

struct A
{
   void func() {}
};

struct B
{
};

template <typename T>
void anotherFunc(T t)
{
   //do t.func() here if T implements func, just do nothing if it doesn't.
}

Is this possible somehow?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
TravisG
  • 2,373
  • 2
  • 30
  • 47
  • Yes, it's possible. Google for SFINAE and C++11 (if you are using it). – kec May 01 '14 at 19:45
  • see also [is there an equivalent for __if_exists in gnu C++](http://stackoverflow.com/questions/3779466/is-there-an-equivalent-for-if-exists-in-gnu-c) – Richard Chambers May 01 '14 at 20:24

1 Answers1

6
// type_sink takes a type, and discards it.  type_sink_t is a C++1y style using alias for it
template<typename T> struct type_sink { typedef void type; };
template<typename T> using type_sink_t = typename type_sink<T>::type;

// has_func is a traits class that inherits from `true_type` iff the expression t.func()
// is a valid one.  `std::true_type` has `::value=true`, and is a good canonical way to
// represent a compile-time `bool`ean value.
template<typename T,typename=void> struct has_func : std::false_type {};
template<typename T> struct has_func<
  T,
  type_sink_t< decltype( std::declval<T&>().func() ) >
> : std::true_type {};

// helpers for tag dispatching.
namespace helper_ns {
  template<typename T> void anotherFunc( T&& t, std::false_type /* has_func */ ) {}
  template<typename T> void anotherFunc( T&& t, std::true_type /* has_func */ ) {
    std::forward<T>(t).func();
  }
}
// take the type T, determine if it has a .func() method.  Then tag dispatch
// to the correct implementation:
template<typename T> void anotherFunc(T t) {
  helper_ns::anotherFunc( std::forward<T>(t), has_func<T>() );
}

is a C++11 solution that does tag dispatching on a traits class that determines if t.func() is a valid expression.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • @0x499602D2: Why what? – Jan Hudec May 01 '14 at 20:02
  • 1
    @Yakk: Why do you need the `type_sink`? Removing it and leaving just the `decltype(std::declval().func())` seems to work. – kec May 01 '14 at 20:05
  • 4
    @JanHudec It's a good solution, but I was expecting something more idiomatic like `template auto helper(T&& t) -> decltype(std::declval().f(), void()) { t.f(); } void helper(...) { }` – David G May 01 '14 at 20:06
  • 1
    @0x499602D2: What about making it an answer? That does look much simpler and seems sufficient. – Jan Hudec May 01 '14 at 20:09
  • @Yakk I also am not trying to be rude. I apologize if you're getting that impression. – David G May 01 '14 at 20:11
  • @user3521733 won't work if `func()` returns non-`void`. So you need to deal with that. – Yakk - Adam Nevraumont May 01 '14 at 20:18
  • @0x499602D2 I like doing one thing at a time when writing template meta programming. `type_sink` takes a type and throws it away. `has_func` checks if `t.func()` is a valid expression, then feeds it to a `type_sink`, in the `true` specialization. Finally, we avoid function `template` specialization and SFINAE in function invocation and use tag dispatching, which is far easier to debug, and scales better than raw SFINAE. Do one thing at a time, and do it robustly. Each piece can be understood on its own as a bonus. – Yakk - Adam Nevraumont May 01 '14 at 20:20
  • And now with comments for each part of the code. As an aside, @0x499602d2, what happens if I pass a non-standard layout type to C style `...` functions again? I forget. – Yakk - Adam Nevraumont May 01 '14 at 20:24
  • @Yakk I didn't go so far as to take that into account but we can still protect against that overload being called by using an `std::enable_if<>` guard with `std::is_standard_layout<>` – David G May 01 '14 at 20:30
  • @mooingduck I'd do `templatestd::result_of_t< callFunc(T&& t, int)->decltype(std::forwardt.func()){return t.func();} void callFunc(T&& t,...){} callFunc` instead (as we know `int` can be passed to `...` safely). But at this point, why not just do tag dispatching? – Yakk - Adam Nevraumont May 01 '14 at 22:37
  • @Yakk: Because then you have to figure out how to come up with a tag, and I've never liked any existing methods for detecting members automatically. FirstChoice + SecondChoice + SFINAE is just much simpler to understand. Also easily extendable. – Mooing Duck May 01 '14 at 22:38
  • @Yakk: So I tried it with a `func()` that returns a type: `struct A { int func() { return 1; } };`, and it seems to fail with or without the `type_sink`. – kec May 02 '14 at 02:30
  • @user3521733 compiler? (and make sure `const`ness and `T`'s type are consistent) – Yakk - Adam Nevraumont May 02 '14 at 10:01
  • 1
    @Yakk: I tried it with g++ 4.9.0, clang++ 3.4, and g++ 4.8.1. The code is http://coliru.stacked-crooked.com/a/9c2a7c546b2df240. – kec May 02 '14 at 12:12
  • @user3521733 oops, typo: http://coliru.stacked-crooked.com/a/2c6bb1e76263c56e -- `type_sink` is supposed to map everything to `void` after evaluating the type -- it is a 'sink', a place where types go away. I did `typedef T type` instead of `typedef void type`. Fixed above, link to live example. – Yakk - Adam Nevraumont May 02 '14 at 15:29
  • I thought `std::forward` only works with a universal reference. – kec Dec 16 '14 at 15:05
  • @kec it is usually used with universal references. I would write the above differently today: I would probably use a universal reference in `anotherFunc` add some `decay_t`, use `void(expression)` instead of `type_sink<>`, change how I wrote `has_func`, and a bunch of other changes. – Yakk - Adam Nevraumont Dec 16 '14 at 15:16