XTF
XTF

Reputation: 1101

Only use templated overload when other overloads fail

Is there a way to ensure the template overload isn't selected unless all other overloads fail, without resorting to enable_if?

Int should be handled by the long overload, but it's being handled by the template overload, which the compiler doesn't like.

class SetProxy {
public:
  void operator=(const TemplateString& value) {
    dict_.SetValue(variable_, value);
  }

  template<class T>
  void operator=(const T& value) {
    dict_.SetValue(variable_, TemplateString(value.data(), value.size()));
  }

  void operator=(long value) {
    dict_.SetIntValue(variable_, value);
  }
}

Upvotes: 0

Views: 90

Answers (3)

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153830

The problem with the code in the question is that the template is the best match if an argument convertible to long or TemplateString is passed: the template doesn't need to do a conversion while calling the other functions would involve a conversion.

The desired behavior can be achieved even without the use of std::enable_if although I think prohibiting the use of std::enable_if is a made-up requirements: even if you can't use C++2011 or Boost, it is extremely simple to implement std::enable_if. Implementing some of the desired type traits is a bit harder but doable. Restricting its use effectively means that you'll need to implement essentially the same logic in a more or less contrived way which doesn't say what it really does. For example, this code doesn't use std::enable_if or SFINAE but there is an extra object actually be created which wouldn't be needed if SFINAE had been used:

#include <iostream>
#include <string>

class SetProxy {
    template <typename F>
    struct helper {
        helper(F const& v): value_(v) {}
        F const& value_;
    };

    template<typename F>
    void aux(helper<F> value, ...) {
        std::cout << "template " << value.value_ << "\n";
    }
    template<typename F>
    void aux(long value, int) {
        std::cout << "long: " << value << "\n";
    }
    template<typename F>
    void aux(std::string const& value, int) {
        std::cout << "string: " << value << "\n";
    }
public:
    template<typename T>
    void operator=(const T& value) {
        this->aux<T>(value, 0);
    }
};

struct foo {};
std::ostream& operator<< (std::ostream& out, foo const&) {
    return out << "foo";
}

int main()
{
    SetProxy p;
    p = 17l;
    p = 17;
    p = foo();
    p = "hello";
    p = std::string("hello");
}

It doesn't use the types from the original question because I don't have them accessible and I didn't feel like typing things irrelevant to the actual question. Note that this effectively contains the important bits of an implementation of std::is_convertible. Internally, it is necessary to forward to another template because the assignment operator cannot have a variable argument list: since there are viable conversions from int to helper<int>, there would be an ambiguity if there weren't anything else to distinguish the types, a variable argument list is used to make the template version a bit worse.

Just for reference, here is the in my opinion more readable version using std::enable_if:

class SetProxy {
public:
    template <typename T>
    typename std::enable_if<!std::is_convertible<T, long>::value
                            && !std::is_convertible<T, std::string>::value, void>::type
    operator= (T const& value) {
        std::cout << "template: " << value << "\n";
    }
    void operator= (long value) {
        std::cout << "long: " << value << "\n";
    }
    void operator= (std::string value) {
        std::cout << "std::string: '" << value << "'\n";
    }
};

Upvotes: 0

brendanw
brendanw

Reputation: 601

It's not unexpected that int matches the template version here. Template argument matching will get a positive match for const int& and pass that to overload resolution. const int& is a better match for int than long is. If you want to avoid calling the template version for int types, then I suggest you add an explicit overload for int.

Upvotes: 0

Mark B
Mark B

Reputation: 96241

Why would int be handled by the long overload? They aren't the same type, and the template with T = int is a perfect match for int while long is not a perfect match.

Can you give us more information about the underlying problem you're trying to solve?

Upvotes: 5

Related Questions