Home > other >  Resolving ambiguity in template overload resolution
Resolving ambiguity in template overload resolution

Time:06-01

I have a heavily-templated C 17 library in which I need to define a template structure for various types. For the sake of the example, let's say that I need specializations for a set of types (e.g. strings, integral types, etc.), and a specialization for any class (i.e. anything satisfying std::is_class<T>) for which I don't yet have a specialization.

I currently have the following code:

#include <string>
#include <string_view>
#include <iostream>

template<typename T, typename Enable = void>
struct A;

template<typename String>
struct A<String,
         std::enable_if_t<
            std::is_same_v<
                std::decay_t<String>,
                std::string>
         || std::is_same_v<
                std::decay_t<String>,
                std::string_view>>>
{
    static void f(String&&) {
        std::cout << "In String specialization" << std::endl;
    }
};

template<typename Object>
struct A<Object,
         std::enable_if_t<
            std::is_class_v<std::decay_t<Object>>>>
{
    static void f(Object&&) {
        std::cout << "In Object specialization" << std::endl;
    }
};

template<typename T>
void g(T&& t) {
    A<T>::f(t);
}

int main() {
    std::string str;
    g(str);
}

This code does not compile, because std::string is a class and therefore matches both specializations. I get an error: ambiguous partial specializations of 'A<std::string &>'.

What would be the idiomatic way of resolving this kind of problem, i.e. defining specializations that may overlap with previous specializations but should be given a lower priority?

CodePudding user response:

I don't know if this is a good solution for you in the general sense, but in the example in your question, you can simply do:

template<typename Object>
struct A<Object,
         std::enable_if_t<
            std::is_class_v<std::decay_t<Object>> && 
            !std::is_same_v<std::string> && !std::is_same_v<std::string_view>>>
{ ... };

(might need some std::decay_ts in there I guess, and you can probably factor out that test).

Demo

Also, since you're on C 17, if constexpr might provide a cleaner solution than lots of SFINAE trickery, which has always been a bit of a hack, IMO.

No chance of moving to C 20, I suppose?

CodePudding user response:

You could negate the condition for the more general is_class case:

// helper trait:
template<class T>
inline constexpr bool is_stringish_v = std::is_same_v<std::decay_t<T>, std::string> ||
                                       std::is_same_v<std::decay_t<T>, std::string_view>;
template <typename T, typename Enable = void>
struct A;

template <typename String>
struct A<String, std::enable_if_t<is_stringish_v<String>>> {
    static void f(String&&) {
        std::cout << "In String specialization" << std::endl;
    }
};

// in here, you negate it:
template <typename Object>
struct A<Object, std::enable_if_t<!is_stringish_v<Object>&&
                                  std::is_class_v<std::decay_t<Object>>>> 
{
    static void f(Object&&) {
        std::cout << "In Object specialization" << std::endl;
    }
};
  • Related