Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call a templated function if it exists, and something else otherwise?

I want to do something like

template <typename T> void foo(const T& t) {    IF bar(t) would compile       bar(t);    ELSE       baz(t); } 

I thought that something using enable_if would do the job here, splitting up foo into two pieces, but I can't seem to work out the details. What's the simplest way of achieving this?

like image 434
Jesse Beder Avatar asked Sep 06 '09 17:09

Jesse Beder


People also ask

How do you call a template function in C++?

A function template starts with the keyword template followed by template parameter(s) inside <> which is followed by the function definition. In the above code, T is a template argument that accepts different data types ( int , float , etc.), and typename is a keyword.

When the actual code for a template function is generated?

A code for template function is generated when the function is instantiated. The functions are often instantiated when they are first time called (in the code), but there are other ways to instantiate a function - do a so-called 'explicit instantiation'.

How do I instantiate a template function?

To instantiate a template function explicitly, follow the template keyword by a declaration (not definition) for the function, with the function identifier followed by the template arguments. template float twice<float>(float original); Template arguments may be omitted when the compiler can infer them.

What is a templated function?

Function templates. Function templates are special functions that can operate with generic types. This allows us to create a function template whose functionality can be adapted to more than one type or class without repeating the entire code for each type. In C++ this can be achieved using template parameters.


2 Answers

There are two lookups that are done for the name bar. One is the unqualified lookup at the definition context of foo. The other is argument dependent lookup at each instantiation context (but the result of the lookup at each instantiation context is not allowed to change behavior between two different instantiation contexts).

To get the desired behavior, you could go and define a fallback function in a fallback namespace that returns some unique type

namespace fallback {   // sizeof > 1   struct flag { char c[2]; };   flag bar(...); } 

The bar function will be called if nothing else matches because the ellipsis has worst conversion cost. Now, include that candidates into your function by a using directive of fallback, so that fallback::bar is included as candidate into the call to bar.

Now, to see whether a call to bar resolves to your function, you will call it, and check whether the return type is flag. The return type of an otherwise chosen function could be void, so you have to do some comma operator tricks to get around that.

namespace fallback {   int operator,(flag, flag);    // map everything else to void   template<typename T>    void operator,(flag, T const&);    // sizeof 1   char operator,(int, flag); } 

If our function was selected then the comma operator invocation will return a reference to int. If not or if the selected function returned void, then the invocation returns void in turn. Then the next invocation with flag as second argument will return a type that has sizeof 1 if our fallback was selected, and a sizeof greater 1 (the built-in comma operator will be used because void is in the mix) if something else was selected.

We compare the sizeof and delegate to a struct.

template<bool> struct foo_impl;  /* bar available */ template<> struct foo_impl<true> {   template<typename T>   static void foo(T const &t) {     bar(t);   } };  /* bar not available */ template<> struct foo_impl<false> {   template<typename T>   static void foo(T const&) {     std::cout << "not available, calling baz...";   } };  template <typename T> void foo(const T& t) {    using namespace fallback;     foo_impl<sizeof (fallback::flag(), bar(t), fallback::flag()) != 1>      ::foo(t); } 

This solution is ambiguous if the existing function has an ellipsis too. But that seems to be rather unlikely. Test using the fallback:

struct C { }; int main() {   // => "not available, calling baz..."   foo(C()); } 

And if a candidate is found using argument dependent lookup

struct C { }; void bar(C) {   std::cout << "called!"; } int main() {   // => "called!"   foo(C()); } 

To test unqualified lookup at definition context, let's define the following function above foo_impl and foo (put the foo_impl template above foo, so they have both the same definition context)

void bar(double d) {   std::cout << "bar(double) called!"; }  // ... foo template ...  int main() {   // => "bar(double) called!"   foo(12); } 
like image 90
Johannes Schaub - litb Avatar answered Oct 04 '22 09:10

Johannes Schaub - litb


litb has given you a very good answer. However, I wonder whether, given more context, we couldn't come up with something that's less generic, but also less, um, elaborate?

For example, what types can be T? Anything? A few types? A very restricted set which you have control over? Some classes you design in conjunction with the function foo? Given the latter, you could simple put something like

typedef boolean<true> has_bar_func; 

into the types and then switch to different foo overloads based on that:

template <typename T> void foo_impl(const T& t, boolean<true> /*has_bar_func*/); template <typename T> void foo_impl(const T& t, boolean<false> /*has_bar_func*/);  template <typename T> void foo(const T& t) {   foo_impl( t, typename T::has_bar_func() ); } 

Also, can the bar/baz function have just about any signature, is there a somewhat restricted set, or is there just one valid signature? If the latter, litb's (excellent) fallback idea, in conjunction with a meta-function employing sizeof might be a bit simpler. But this I haven't explored, so it's just a thought.

like image 35
sbi Avatar answered Oct 04 '22 09:10

sbi