I am trying to use class template partial specialization to check if certain expressions are valid for a given type. It compiles with gcc but not with msvc nor clang. Both msvc and clang complain redefinition of partial specialization.
Error message from clang:
prog.cc:9:8: error: redefinition of 'Trait<T, std::void_t<decltype(foo(std::declval(), std::declval()))>>' struct Trait<T, std::void_t<decltype(foo(std::declval(), std::declval()))>> {}; prog.cc:7:8: note: previous definition is here struct Trait<T, std::void_t<decltype(foo(std::declval()))>> {};
#include <type_traits>
#include <utility>
template <class T, class = void>
struct Trait {};
template <class T>
struct Trait<T, std::void_t<decltype(foo(std::declval<T>()))>> {};
template <class T>
struct Trait<T, std::void_t<decltype(foo(std::declval<T>(), std::declval<T>()))>> {};
struct X {};
void foo(X) {}
struct Y {};
void foo(Y, Y) {}
using XTrait = Trait<X>;
using YTrait = Trait<Y>;
int main() {}
I have checked the wording of class template partial specialization, but I can't find anything related to this use case. I also found this stackoverflow answer, it says
But there isn't a rule that states that partial specializations can never be ambiguous.
I'm not sure if it is related to this error.
Why those compilers have different result? Which one behaves correctly? Is there a workaround to fix this template specialization redefinition problem? Any help is welcome, thanks.
My goal is to unify the way to call a user defined function as some parameter(s) may be omitted. There also comes with the problem of ordering, as a type may satisfy multiple different call expressions. I come up with this workaround in the end, it is a lot verbose and use function overload instead.
#include <type_traits>
#include <utility>
#include <iostream>
template <size_t> struct Order {};
template <class, size_t>
struct TraitImpl : std::false_type {};
template <class T> struct TraitImpl<T, 0> : std::true_type {
static void InvokeFoo(T v1, T v2) { foo(v1, v2); }
};
template <class T> struct TraitImpl<T, 1> : std::true_type {
static void InvokeFoo(T v1, T) { foo(v1); }
};
template <class T, class = std::void_t<decltype(foo(std::declval<T>(), std::declval<T>()))>>
auto TraitHlprImpl(Order<0>) { return TraitImpl<T, 0>{}; }
template <class T, class = std::void_t<decltype(foo(std::declval<T>()))>>
auto TraitHlprImpl(Order<1>) { return TraitImpl<T, 1>{}; }
template <class T>
auto TraitHlprImpl(Order<2>) { return TraitImpl<T, 2>{}; }
template <class T, size_t v = 0, class = decltype(TraitHlprImpl<T>(Order<v>{}))>
auto TraitHlpr(int) {
return TraitHlprImpl<T>(Order<v>{});
}
template <class T, size_t v = 0>
auto TraitHlpr(float) {
if constexpr (v < 20)
return TraitHlpr<T, v+1>(0);
else
static_assert(std::is_same_v<T, T*>);
}
template <class T>
struct Trait : decltype(TraitHlpr<T>(0)) {};
struct X {};
void foo(X) { std::cout << "X" << std::endl; }
struct Y {};
void foo(Y, Y) { std::cout << "Y, Y" << std::endl; }
struct Z {};
void foo(Z, Z) { std::cout << "Z, Z" << std::endl; }
void foo(Z) { std::cout << "Z" << std::endl; }
int main() {
Trait<X>::InvokeFoo(X{}, X{});
Trait<Y>::InvokeFoo(Y{}, Y{});
Trait<Z>::InvokeFoo(Z{}, Z{});
}