Normally you can partially specialize a template class against an instantiated template class. For instance
template<class T>
struct specialize_me {};
template<class T>
struct specialize_me<std::vector<T>> {
static const int foo = 3;
};
the template class specialize_me
is partially specialized against the instantiated template class std::vector<T>
. This specialization is selected when specialize_me
is instantiated with std::vector<T>
, for any class T
.
int main() {
std::cout << specialize_me<std::vector<int>>::foo; // Compiles.
}
However, I cannot figure out how to specialize a template template class against an instantiated nested template class:
// Nested template class.
template<class T>
struct Either {
template<class U>
struct Or {};
};
template<template<class> class T>
struct specialize_me_2 {};
template<class T>
struct specialize_me_2<Either<T>::template Or> {
static const int foo = 3;
};
In this case, the specialization is not selected when I instantiate specialize_me_2
with the class Either<T>::template Or
for any class T
. My guess is that this happens because the compiler would have to confirm or deny, "There exists a T
such that Either<T>::template Or
is the same type as the specialize_me_2
instantiation" in order to select my specialization, and it is not programmed nor specified to do so.
int main() {
std::cout << specialize_me_2<Either<int>::Or>::foo; // Does not compile. 'foo' is not a member of specialize_me_2<Either<int>::Or>.
}
Is there a way to specialize specialize_me_2
such that the specialization is selected whenever specialize_me_2
is instantiated with Either<T>::Or
for any T
?
This Either
struct is eventually going to represent an error-carrying type, so Either<T>
denotes that T
is the error type, and Either<T>::Or<U>
denotes that U
is the type being carried by the successful computation.
If this is impossible, I might still be able to use #define
s to enable you to define Either<T>
for each T
as it is needed, where #define
also includes the specialize_me_2
specialization for that particular Either<T>::Or
. Indeed, I intend to use the Either
struct in programs by writing template<class T> using FooError = Either<Foo>::Or<T>
anyway and then write FooError<Bar>
, FooError<Quux>
and so on, so using this wouldn't be a huge break from the intended usage.
Intriguing problem.
To solve it without too much pain... if you can add new using
type inside Or
template <typename T>
struct Either
{
template <typename>
struct Or
{ using specialOrType = T; };
};
then you can add a second template parameter, a typename with void
as default, in specialize_me_2
template <template <typename> class C, typename = void>
struct specialize_me_2
{ static const int foo = 2; };
and using SFINAE over specialOrType
template <typename ...>
using myVoidT = void;
template <template <typename> class C>
struct specialize_me_2<C, myVoidT<typename C<void>::specialOrType>>
{
using T = typename C<void>::specialOrType;
static const int foo = 3;
};
you get your working specialization.
Instead of myVoidT
, starting from C++17, you can obviously use std::void_t
.
Observe that this way you can't deduce the original T
type but you can recover it through specialOrType
.
Observe also that this require (as pointed by aschepler) that Or<void>
is a valid specialization. If this isn't the case, you should choose another type X
so that Or<X>
is a valid specialization for all Either<T>
. Suppose that, by example, Or<int>
is a valid specialization for every Either<T>
, the specialization become
template <template <typename> class C>
struct specialize_me_2<C, myVoidT<typename C<int>::specialOrType>>
{
using T = typename C<int>::specialOrType;
static const int foo = 3;
};
The following is a full working example
#include <iostream>
template <typename ...>
using myVoidT = void;
template <typename>
struct NoEither
{ };
template <typename T>
struct Either
{
template <typename>
struct Or
{ using specialOrType = T; };
};
template <template <typename> class C, typename = void>
struct specialize_me_2
{ static const int foo = 2; };
template <template <typename> class C>
struct specialize_me_2<C, myVoidT<typename C<void>::specialOrType>>
{
using T = typename C<void>::specialOrType;
static const int foo = 3;
};
int main ()
{
std::cout << specialize_me_2<NoEither>::foo << std::endl;
std::cout << specialize_me_2<Either<int>::template Or>::foo << std::endl;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With