Templates: Only execute method if class has it
Asked Answered
A

1

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?

Ananias answered 1/5, 2014 at 19:41 Comment(2)
Yes, it's possible. Google for SFINAE and C++11 (if you are using it).Ortolan
see also is there an equivalent for __if_exists in gnu C++Cannikin
C
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.

Coronal answered 1/5, 2014 at 19:52 Comment(17)
@0x499602D2: Why what?Smew
@Yakk: Why do you need the type_sink? Removing it and leaving just the decltype(std::declval<T&>().func()) seems to work.Ortolan
@JanHudec It's a good solution, but I was expecting something more idiomatic like template<typename T> auto helper(T&& t) -> decltype(std::declval<T>().f(), void()) { t.f(); } void helper(...) { }Porphyrin
@0x499602D2: What about making it an answer? That does look much simpler and seems sufficient.Smew
@Yakk I also am not trying to be rude. I apologize if you're getting that impression.Porphyrin
@user3521733 won't work if func() returns non-void. So you need to deal with that.Coronal
@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.Coronal
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.Coronal
@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<>Porphyrin
@mooingduck I'd do template<class T>std::result_of_t< callFunc(T&& t, int)->decltype(std::forward<T>t.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?Coronal
@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.Bartizan
@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.Ortolan
@user3521733 compiler? (and make sure constness and T's type are consistent)Coronal
@Yakk: I tried it with g++ 4.9.0, clang++ 3.4, and g++ 4.8.1. The code is coliru.stacked-crooked.com/a/9c2a7c546b2df240.Ortolan
@user3521733 oops, typo: 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.Coronal
I thought std::forward only works with a universal reference.Ortolan
@Ortolan 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.Coronal

© 2022 - 2024 — McMap. All rights reserved.