How to resolve ambiguity in overloaded functions using SFINAE

后端 未结 4 1628
太阳男子
太阳男子 2021-02-08 22:21

I have an incredibly exciting library that can translate points: it should work with any point types

template
auto translate_point(T &p, int x         


        
相关标签:
4条回答
  • 2021-02-08 22:53

    In this case, both of your overloads are under-constrained. You can't really call the second one with StupidPoint, but that isn't observable at the point of overload resolution. If you constrain both properly, you'll remove the ambiguity in this case:

    template<class T>
    auto translate_point(T &p, int x, int y) -> decltype(p.x += x, p.y += y, void()) { ... };
    
    template<class T>
    auto translate_point(T &p, int x, int y) -> decltype(p[1] += y, void()) { ... } // prefer checking 1, so you don't allow an operator[] that takes a pointer
    

    Now, if operator[] returned an int& instead, this would still be ambiguous. In that case, you'd need a way to order the two overloads (maybe with an extra argument that is either int or ...?), or simply disallow that case. That's a separate design decision.

    0 讨论(0)
  • 2021-02-08 22:56

    If you want to give precedence to the case having public x/y, you can do this:

    template<class T>
    auto translate_point_impl(int, T &p, int x, int y) -> decltype(p.x, p.y, void())
    {
        p.x += x;
        p.y += y;
    }
    
    template<class T>
    auto translate_point_impl(char, T &p, int x, int y) -> decltype(p[0], void())
    {
        p[0] += x;
        p[1] += y;
    }
    
    template<class T>
    void translate_point(T &p, int x, int y) {
        translate_point_impl(0, p, x, y);
    }
    

    It goes without saying that the opposite configuration is given by switching the types of the first parameter.


    If you have three or more options (says N), you can use a trick based on templates.
    Here is the example above once switched to such a structure:

    template<std::size_t N>
    struct choice: choice<N-1> {};
    
    template<>
    struct choice<0> {};
    
    template<class T>
    auto translate_point_impl(choice<1>, T &p, int x, int y) -> decltype(p.x, p.y, void()) {
        p.x += x; p.y += y;
    }
    
    template<class T>
    auto translate_point_impl(choice<0>, T &p, int x, int y) -> decltype(p[0], void()) {
        p[0] += x;
        p[1] += y;
    }
    
    template<class T>
    void translate_point(T &p, int x, int y) {
        // use choice<N> as first argument
        translate_point_impl(choice<1>{}, p, x, y);
    }
    

    As you can see, now N can assume any value.

    0 讨论(0)
  • 2021-02-08 23:06

    With SFINAE, I would do something like this:

    template<class T, bool>
    auto translate_point(T &p, int x, int y) -> decltype(p[0], void())
    {
        p[0] += x;
        p[1] += y;
    }
    
    template<class T, bool = std::is_base_of<StupidPoint, T>::value>
    auto translate_point(T &p, int x, int y) -> decltype(p.x, p.y, void())
    {
        p.x += x;
        p.y += y;
    }
    

    By doing this, when T = (class with StupidPoint as base class), the second overload will be called.

    But it is easier with a simple overload, as pointed out by m.s.

    0 讨论(0)
  • 2021-02-08 23:10

    You could provide an overload for StupidPoint:

    auto translate_point(StupidPoint &p, int x, int y)
    {
        p.x += x;
        p.y += y;
    }
    

    live example


    Another solution:

    Since operator[] is const for StupidPoint, you can check this in your SFINAE condition:

    template<class T>
    auto translate_point(T &p, int x, int y) -> decltype(p[0] += 0, void())
    {
        p[0] += x;
        p[1] += y;
    }
    

    live example


    You could also use a different type-traits based approach to select the appropriate translate_point function:

    template<typename T, typename = void>
    struct has_x_y : std::false_type { };
    
    template<typename T>
    struct has_x_y<T, decltype(std::declval<T>().x, std::declval<T>().y, void())> : std::true_type { };
    
    template<typename T, typename = void>
    struct has_index : std::false_type { };
    
    template<typename T>
    struct has_index<T, decltype(std::declval<T>().operator[](0), void())> : std::true_type { };
    
    template<class T>
    std::enable_if_t<has_x_y<T>::value> translate_point(T &p, int x, int y)
    {
        p.x += x;
        p.y += y;
    }
    
    template<class T>
    std::enable_if_t<!has_x_y<T>::value && has_index<T>::value> translate_point(T &p, int x, int y)
    {
        p[0] += x;
        p[1] += y;
    }
    

    live example

    0 讨论(0)
提交回复
热议问题