views:

728

answers:

7

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?

A: 

Are you not able to use full specialisation here (or overloading) on foo. By say having the function template call bar but for certain types fully specialise it to call baz?

not all properties subdue to "whether it compiles" check can be verified with type inference that happens when you call a template function.
Pavel Shved
+2  A: 

EDIT: I spoke too soon! litb's answer shows how this can actually be done (at the possible cost of your sanity... :-P)

Unfortunately I think the general case of checking "would this compile" is out of reach of function template argument deduction + SFINAE, which is the usual trick for this stuff. I think the best you can do is to create a "backup" function template:

template <typename T>
void bar(T t) {   // "Backup" bar() template
    baz(t);
}

And then change foo() to simply:

template <typename T>
void foo(const T& t) {
    bar(t);
}

This will work for most cases. Because the bar() template's parameter type is T, it will be deemed "less specialised" when compared with any other function or function template named bar() and will therefore cede priority to that pre-existing function or function template during overload resolution. Except that:

  • If the pre-existing bar() is itself a function template taking a template parameter of type T, an ambiguity will arise because neither template is more specialised than the other, and the compiler will complain.
  • Implicit conversions also won't work, and will lead to hard-to-diagnose problems: Suppose there is a pre-existing bar(long) but foo(123) is called. In this case, the compiler will quietly choose to instantiate the "backup" bar() template with T = int instead of performing the int->long promotion, even though the latter would have compiled and worked fine!

In short: there's no easy, complete solution, and I'm pretty sure there's not even a tricky-as-hell, complete solution. :(

j_random_hacker
+23  A: 

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);
}
Johannes Schaub - litb
Very nice! This works well, and is quite elegant!
Jesse Beder
Holy crap! O_o Where did you come up with the idea of using the comma operator that way? (Figuring out what you did there took 15 minutes just by itself...)
j_random_hacker
BTW I mean "Holy crap!" in a good way :)
j_random_hacker
I read about it in some usenet post: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1
Johannes Schaub - litb
very nice solution, hmm, I'm right now thinking I'm gonna take some hours and try to macro this even further..
Viktor Sehr
+1 nice indeed. I think you should be able to simplify it even more and get rid of all but your first comma operator overload and also do sizeof( bar(t), flag()) instead of sizeof(flag(),bar(t), flag()). Not sure what benefit is gained from doing all of that.
Faisal Vali
@Faisal Vali, thanks. i didn't do it with only one `op,` invocation, because there are types that overload templated `op,` and that accept flag as second argument (like a `del << a, b;`, which has a `delim operator,(delim,T const`). So doing `(bar(t), flag())` would yield an ambiguity. We also couldn't swap the order, because `bar(t)` could have type `void`. Having an `op,` accepting flag as first argument is much less likely of course - thus that three-way thing :)
Johannes Schaub - litb
Hmm - good point - but here's an alternative - not sure if everyone would agree that it is simpler but it is less typing ;)Instead of adding two additional comma overloads to hijack ADL, we could try and turn ADL off by adding a block declaration.So add the following after 'using namespace fallback;': using fallback::operator,; void operator,(flag,int); Then you can do sizeof(bar(t),flag()) with impunity i feel. The block declaration blocks ADL (hides boost or global ops), will never get called, does not hide the correct ',op' nor does it hide the builtin ',op' - your thoughts?
Faisal Vali
@Faisal, ah nice idea :) But i think the non-using-block-declarations stopping ADL is a c++0x thing :( GCC does ADL nonetheless, and comeau up to including "BETA 8/4/03". (I was too in the impression those declarations deactivate ADL in C++03, but seems they added that in a later defect fix or something). Good point though, lemme see whether we can (ab)-use member function declarations instead :)
Johannes Schaub - litb
Hmm, sadly, when transforming `a, b` into `operator,(a, b)` then the lookup for candidates using unqualified/ADL ignores member functions according to `13.3.1.2/3`. So one cannot use member functions to disable ADL -.-
Johannes Schaub - litb
Access checks on bar(t) are done last, so it seems this method fails if `bar` is a class and `bar::bar(T t)` is private.
MSalters
@MSalters, you are right, good point there. We would get an ambiguity error if there is a `bar` struct at global scope - didn't consider that at all. If it happens, then for all `T`: so it at least doesn't come at a surprise. For this case, there is the alternative of providing `using declarations` for all `bar` functions that can be reached by unqualified lookup from the definition context (ADL lookup will work nontheless, but ADL won't find structs anyway xD). This will make it non-ambiguous (see previous versions of this answer), and will hide the struct from being looked up.
Johannes Schaub - litb
Removed the array references, which aren't needed in this code. I suppose it looks clearer now.
Johannes Schaub - litb
@MSalters, on the other side, there could be a macro `#define foo ...` that could get into the way :) I think in the end, such name collisions can always happen. A call that resolves to a type creating a temporary looks pretty silly to me xD
Johannes Schaub - litb
I think your fallback namespace can be reduced to:struct _1{char c[1];};struct _2{char c[2];};_1 bar(...);_2 operator,(_1, _1);_2 operator,(_2, _1);
Vadim Ferderer
@Vadim, If you do then `_1, bar(t), _1` and if `bar` would return `delim` which has a `delim operator,(delim,T)` or similar, it could give us any type whatsoever back. I also don't understand the difference between `_1` and `_2`. It's possible both have the same sizeof value. Can you please elaborate on that alternative?
Johannes Schaub - litb
How can _1 and _2 be of the same size? The first has char[1] and the second char[2]. It also doesn't matter, if you have some operators defined, since they wouldn't have an overload (something,_1), so the built-in comma-op would take over and just deliver _1, what is exactly what we want. It works for me.
Vadim Ferderer
They can have the same size if the compiler gives both a sizeof of `2`, `4` or whatever, which is perfectly fine (padding bytes). You don't know whether or not the type has such an operator overload in advance - so you don't know whether or not the builtin comma operator will be used. Try to define: `struct delim { char c[2]; }; template<typename T> delim operator,(delim, T); delim bar(int);` with those, a call to `foo(10);` will wrongly think that there is no matching `bar` function, as far as i can see. That's why my templated `operator,` maps anything else to `void` first.
Johannes Schaub - litb
+2  A: 

If you're willing to limit yourself to Visual C++, you can use the __if_exists and __if_not_exists statements.

Handy in a pinch, but platform specific.

ijprest
That doesn't seem to consider whether the call actually would compile, though. Consider a call to "foo(1)" but a function "bar(char*)" exists.
Johannes Schaub - litb
Interesting, I didn't know about those. +1 (though litb's criticism is correct).
j_random_hacker
hmm - wow, i had no idea these existed - thanks!
Faisal Vali
+4  A: 

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.

sbi
+1 Good point, i think. He should first think about this stuff before doing too complicated things. Using ADL like with the `swap` function could be useful too. Anyway, thanks for the praises :)
Johannes Schaub - litb
Well, the set of `T` that have a `bar` isn't *completely* arbitrary, but it's more than just a few types. Plus, specializing a `has_bar_func` for the different types would be a (small, but significant) code duplication. And while litb's idea is very clever, it's not that "elaborate" (there are plenty of examples in boost that are significantly more complicated, I think)
Jesse Beder
@litb: When I saw the `struct flag { char c[2]; };` at the top of a thoroughly explaining answer, I knew it was yours although your name still wasn't visible on the screen. `:)`
sbi
What's more, this is not the first time a pattern like this has come up. In the past, I've done something like what you suggest, but it's a bit tedious and inelegant. This time, I was hoping for (and got!) a "simple" and elegant solution.
Jesse Beder
@Jesse: If that's so, then go and implement litb's idea. It surely seems a lot more general than what I came up with. If that fit's the bill, then it's great.
sbi
@sbi, sorry, I didn't mean to be so harsh. Your suggestion is definitely a good one, and it never hurts to suggest that I think some more :)
Jesse Beder
+1, I like this clean and simple approach. Can I suggest creating a separate traits class, e.g. foo_traits<T>, for containing has_bar_func? That way you can also handle fundamental types.
j_random_hacker
@Jesse: No need to apologies. I'm sorry if that came across as if I was offended. I wasn't and my statement was purely factual.
sbi
@j_random_hacker: Yep, if you want to do this for fundamental types (or types you cannot change), a traits class would be a way out. That's exactly why I asked for more context. If we know more context, more solutions might be possible.
sbi
+2  A: 
//default

////////////////////////////////////////// 
    template <class T>
    void foo(const T& t){
        baz(t);
    }

//specializations
//////////////////////////////////////////  

    template <>
    void foo(const specialization_1& t){
        bar(t);
    }
    ....
    template <>
    void foo(const specialization_n& t){
        bar(t);
    }
pgast
+1 for sheer simplicity, but as with sbi's solution the problem here is that you need to keep the list of specialisations up-to-date with the list of types that support `bar()` -- and *that* is what the OP is trying to avoid doing.
j_random_hacker
pgast
1)who is to say that "if it will compile" actually gets you the code you really want? 2) changes outside classes can change the target code semantics without any indication whatsoever(add a constructor to one class remove a constructor from another). Also putting the explicit specializations in one header could add ambiguities as well as contribute to 2) above.
pgast
I totally agree with both your points in your last comment -- it could easily make for maintenance hell. But at the same time I think (a) it's an interesting question to ask whether (or to what extent) it is possible to implement the `if_this_would_compile(...)` construct that the OP asks for -- and (b) I think there are cases where the increased DRYness it brings to a design might outweigh the added "action-at-a-distance" complexity. (Many similar but less general constructs, e.g. template arg deduction + SFINAE, already violate your (2) in highly esteemed libraries like Boost.)
j_random_hacker
+1  A: 

I think litb's solution works, but is overly complex. The reason is that he's introducing a function fallback::bar(...) which acts as a "function of last resort", and then goes to great lengths NOT to call it. Why? It seems we have a perfect behavior for it:

namespace fallback {
    template<typename T>
    inline void bar(T const& t, ...)
    {
        baz(t);
    }
}
template<typename T>
void foo(T const& t)
{
    using namespace fallback;
    bar(t);
}

But as I indicated in a comment to litb's original post, there are many reasons why bar(t) could fail to compile, and I'm not certain this solution handles the same cases. It certainly will fail on a private bar::bar(T t)

MSalters
That's got the same behavior as j_random_hacker's code though. The ellipsis is not considered in ranking the overload with other functions if there's no corresponding argument that's passed to the ellipsis. (`13.3.2`, `13.3.3`).
Johannes Schaub - litb
+1 for mentioning the access-checking failure case.
j_random_hacker