I have two related types (duck-typing), and another one that provides similar functionality with a different interface:
namespace a
{
template<typename T>
struct A final {};
using int_t = A<int>;
using float_t = A<float>;
}
namespace b
{
template<typename T>
struct B final {};
using int_t = B<int>;
using float_t = B<float>;
}
namespace foo
{
template<typename T>
struct Foo final {};
using int_t = Foo<int>;
using float_t = Foo<float>;
std::string f(const int_t&)
{
return "f(Foo<int>)\n";
}
std::string g(const int_t&)
{
return "g(Foo<int>)\n";
}
std::string h(const float_t&)
{
return "h(Foo<float>)\n";
}
}
a
and b
are "related", foo
is "similar." Writing a generic f()
works, but is too "greedy":
template<typename T>
std::string f(const T&)
{
return "f(T)\n";
}
// ...
int main()
{
a::int_t a_int;
b::int_t b_int;
foo::int_t foo_int;
std::cout << f(a_int); // "f(T)"
std::cout << f(b_int); // "f(T)"
std::cout << f(foo_int); // "f(Foo<int>)"
std::cout << f(314); // NO! ... "f(T)"
std::cout << "\n";
}
To "tighten" that up a bit, I did the following:
template<typename Tab> struct AB final
{
AB() = delete;
};
template<typename T> struct AB<a::A<T>> final
{
AB() = delete;
using type = a::A<T>;
};
template<typename T> struct AB<b::B<T>> final
{
AB() = delete;
using type = b::B<T>;
};
Now I can write g()
that only works on a::A<T>
and b:B<T>
:
template<typename Tab, typename type_ = typename AB<Tab>::type>
std::string g(const Tab&)
{
return "g(Tab)\n";
}
This works as expected:
std::cout << g(a_int); // "g(Tab)"
std::cout << g(b_int); // "g(Tab)"
std::cout << g(foo_int); // "g(Foo<int>)"
//std::cout << g(314); // YES! doesn't compile
std::cout << "\n";
However, I can't figure out how to set things up to write an h()
that will only work for either a::A<float>
or b::B<float>
, but not any other specialization.
template<typename Tab, typename type_ = typename AB<Tab>::type>
std::string h(const Tab&)
{
return "h(Tab<float>)\n";
}
// ...
a::float_t a_float;
b::float_t b_float;
foo::float_t foo_float;
std::cout << h(a_float); // "h(Tab<float>)";
std::cout << h(b_float); // "h(Tab<float>)";
std::cout << h(foo_float); // "h(Foo<float>)"
std::cout << h(a_int); // NO! ... "h(Tab<float>)";
std::cout << h(b_int); // NO! ... "h(Tab<float>)";
//std::cout << h(foo_int); // YES! doesn't compile
//std::cout << h(314); // YES! doesn't compile
I've tried a passing a template as a template
, but I can't quite figure out the right syntax.
I'd like something that works in C++14.
I can't figure out how to set things up to write an h() that will only work for either
a::A<float>
orb::B<float>
, but not any other specialization.
You could add a constraint.
C++20:
template <template <class> class Tab, class T>
requires(std::same_as<a::float_t, Tab<T>> ||
std::same_as<b::float_t, Tab<T>>)
std::string h(const Tab<T>&) {
return "h(Tab<float>)\n";
}
C++14:
template <template <class> class Tab, class T>
std::enable_if_t<std::is_same<a::float_t, Tab<T>>::value ||
std::is_same<b::float_t, Tab<T>>::value,
std::string>
h(const Tab<T>&) {
return "h(Tab<float>)\n";
}
In both versions, you make the function take a template-template parameter (Tab
) and then check if Tab<T>
is either a::float_t
or b::float_t
SFINAE style to allow for other h
overloads.
If you don't need SFINAE to create other h
overloads, a static_assert
may be preferable:
template <template <class> class Tab, class T>
std::string h(const Tab<T>&) {
static_assert(std::is_same<a::float_t, Tab<T>>::value ||
std::is_same<b::float_t, Tab<T>>::value,
"must be a::float_t or b::float_t");
return "h(Tab<float>)\n";
}