Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

specialization of template template paremeter of template class for nested template class

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 #defines 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.

like image 201
jcarpenter2 Avatar asked Nov 07 '22 05:11

jcarpenter2


1 Answers

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;
 }
like image 97
max66 Avatar answered Nov 14 '22 22:11

max66