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_t
s in there I guess, and you can probably factor out that test).
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;
}
};