0

As one example of a broader problem, given these two overloads, you might think that the array version would take priority when an array is passed:

template <size_t N>
void bar(const char (&)[N]) { 
    std::cout << "array, size=" << N-1 << std::endl;
}
void bar(const char *s)  { 
    std::cout << "raw, size=" << strlen(s) << std::endl;
}

but when passing an array (a string literal is an array), bar("hello"), the latter version (the pointer version) will be called instead.

This particular case has been discussed on SO, and the answer is interesting. But there is a general question here. I want to force the compiler to prefer one overload, and to only abandon that overload only when all legal attempts to call it have failed.

Let's rename them to bar1 and bar2 for clarity:

template <size_t N>
void bar1(const char (&)[N]) { 
    std::cout << "array, size=" << N-1 << std::endl;
}
void bar2(const char *s)  { 
    std::cout << "raw, size=" << strlen(s) << std::endl;
}

Without changing those any further, can we write something like this:

template<typename ...Args>
auto try_bar1_then_bar2(Args&& ...args) -> ??? { 
    ... will first attempt to perfect forward to bar1 ...
    ... only if bar1 cannot be called, fallback to bar2 ...
}

I've used some C++11 in this question, with && for perfect forwarding, but I guess the general question applies to earlier C++ also. Is there a simple, general, way to force a reordering of the overload priority? When a set of functions (with different names?) are (barely) callable, how to control exactly what order they are attempted in?

Community
  • 1
  • 1
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88

2 Answers2

2

Some Expression SFINAE:

template<typename ...Args>
auto try_bar1_then_bar2_IMPL(int, Args&& ...args) -> decltype( bar1(forward<Args>(args)...) ) { 
    cout << "Yes, we can call bar1" << endl;
    return bar1(forward<Args>(args)...);
}
template<typename ...Args>
auto try_bar1_then_bar2_IMPL(char, Args&& ...args) -> void { 
    cout << "No, can't call bar1, calling bar2 instead." << endl;
    return bar2(forward<Args>(args)...);
}
template<typename ...Args>
auto try_bar1_then_bar2(Args&& ...args) -> decltype( try_bar1_then_bar2_IMPL(0, forward<Args>(args)...) ) { 
    return try_bar1_then_bar2_IMPL(0, forward<Args>(args)...);
}

When bar1 cannot be called, the first overload of try_bar1_then_bar2_IMPL is invalid because the decltype in the return type fails. But if bar1 can be called, then both are valid (and are perfectly matched, I think). I've therefore added a dummy parameter in front, an int or char, which tie breaks in favour of the call to bar1.

This is called like so:

try_bar1_then_bar2("hello");  // array, calls array version
try_bar1_then_bar2(+"hello"); // + converts to a pointer, therefore
                                 // this calls the pointer ('raw') version.
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • (Answering own question here). I'm not very good at this overload resolution and tie-breaking. Is my answer correct in general? Any feedback welcome! – Aaron McDaid Jan 31 '15 at 08:45
  • @PiotrS. Thanks, I removed my use of `__PRETTY_FUNCTION__` also. I find it very useful in debugging, but it's not standard. – Aaron McDaid Jan 31 '15 at 08:49
0

You can wrap the arguments with a template to get the desired overloaded function:

#include <cstring>
#include <iostream>

template <typename T>
struct Wrap
{
    const T& value;

    Wrap(const T& value)
    : value(value)
    {}
};

template <typename T>
inline Wrap<T> wrap(const T& value) {
    return Wrap<T>(value);
}


template <size_t N>
void bar(const Wrap<char[N]>&) {
    std::cout << "array, size=" << N-1 << std::endl;
}

void bar(const Wrap<const char *>& s)  {
    std::cout << "raw, size=" << strlen(s.value) << std::endl;
}

template <typename T>
void bar(const T& value) {
    bar(wrap(value));
}


int main(int argc, char* argv[]) {
    const char a[] ="hello";
    const char* s ="hello";
    bar(a);
    bar(s);
}
  • This doesn't work with non-const values. E.g. `char * s; bar(s)`. I guess the general point is that I don't want to look only for perfect matches of arguments. There is always scope for conversions of various kinds. I want `bar1` to have priority and only after the compiler has established that `bar1` cannot be called, regardless of conversions, should it then move to `bar2`. And finally, relevant to your answer, if `bar1` is rejected, I want the compiler to try its best to call `bar2`. I don't want `bar2` to fail just because it doesn't notice that it can convert `char*` to `const char *`. – Aaron McDaid Jan 31 '15 at 13:19