francesco
francesco

Reputation: 7539

specialization of class member of a template class

Let A be a template class, containing an inner struct. I would like to specialize the inner struct (and only that), depending on the template parameter of A. The following code appears to correctly do the job:

#include <iostream>

template <bool rgb>
struct A {
  struct colors;
  A();
};

template <>
struct A<true>::colors { enum : std::size_t { red, green, blue }; };

template <>
struct A<false>::colors { enum : std::size_t { cyan, magenta, yellow, black }; };

template<bool rgb>
A<rgb>::A()
{
    if (rgb) {
        std::cout << "rgb true" << std::endl;
    }
    else {
        std::cout << "rgb false" << std::endl;
    }
}

int main()
{
    using colors_true = A<true>::colors;
    using colors_false = A<false>::colors;

    A<true> at{};
    A<false> af{};
    std::cout << colors_true::red << std::endl;
    std::cout << colors_false::yellow << std::endl;
}

See it live on Coliru.

(The constructor of A is just to illustrate that I am only specializing A::colors)

Now, consider the case when A contains an additional template parameter. I would like to mimick the code above, specializing only the bool parameter. The following code however does not compile:

#include <iostream>

template <bool rgb, int i>
struct A {
  struct colors;
  A();
};

template <int i>
struct A<true, i>::colors { enum : std::size_t { red, green, blue }; };

template <int i>
struct A<false, i>::colors { enum : std::size_t { cyan, magenta, yellow, black }; };

template<bool rgb, int i>
A<rgb, i>::A()
{
    if (rgb) {
        std::cout << "rgb true";
    }
    else {
        std::cout << "rgb false";
    }
    std::cout << " i = " << i << std::endl;
}

int main()
{
    using colors_true = A<true, 2>::colors;
    using colors_false = A<false, 5>::colors;

    A<true, 2> at{};
    A<false, 5> af{};
    std::cout << colors_true::red << std::endl;
    std::cout << colors_false::yellow << std::endl;
}

See it live on Coliru.

The compilation error is:

main.cpp:10:20: error: invalid class name in declaration of 'class A<true, i>::colors'

   10 | struct A<true, i>::colors { enum : std::size_t { red, green, blue }; };

      |                    ^~~~~~

main.cpp:13:21: error: invalid class name in declaration of 'class A<false, i>::colors'

   13 | struct A<false, i>::colors { enum : std::size_t { cyan, magenta, yellow, black }; };

      |                     ^~~~~~

A partial specialization of A, like in the following code

#include <iostream>

template <bool rgb, int i>
struct A {
  struct colors;
  A();
};

template <int i>
struct A<true, i> {
struct colors { enum : std::size_t { red, green, blue }; };
};

template <int i>
struct A<false, i> {
    struct colors { enum : std::size_t { cyan, magenta, yellow, black }; };
};

template<bool rgb, int i>
A<rgb, i>::A()
{
    if (rgb) {
        std::cout << "rgb true";
    }
    else {
        std::cout << "rgb false";
    }
    std::cout << " i = " << i << std::endl;
}

int main()
{
    using colors_true = A<true, 2>::colors;
    using colors_false = A<false, 5>::colors;

    A<true, 2> at{};
    A<false, 5> af{};
    std::cout << colors_true::red << std::endl;
    std::cout << colors_false::yellow << std::endl;
}

does not work either. See it live on Coliru. While the code compiles, the partial specialization of A completely obscures the constructor A::A(), as the output shows. In other words, in the code above the compiler picks up two partially specialized versions of A, where the constructor is not explicitly defined.

As a workaround, I figured out one can use inheritance:

#include <iostream>

template <bool rgb>
struct colors_type;

template <>
struct colors_type<true> {
    struct colors { enum : std::size_t { red, green, blue }; };
};

template <>
struct colors_type<false> {
    struct colors { enum : std::size_t { cyan, magenta, yellow, black }; };
};

template <bool rgb, int i>
struct A : public colors_type<rgb> {
  A();
};

template<bool rgb, int i>
A<rgb, i>::A()
{
    if (rgb) {
        std::cout << "rgb true";
    }
    else {
        std::cout << "rgb false";
    }
    std::cout << " i = " << i << std::endl;
}

int main()
{
    using colors_true = A<true, 2>::colors;
    using colors_false = A<false, 5>::colors;

    A<true, 2> at{};
    A<false, 5> af{};
    std::cout << colors_true::red << std::endl;
    std::cout << colors_false::yellow << std::endl;
}

See it live on Coliru.

Upvotes: 2

Views: 67

Answers (2)

aschepler
aschepler

Reputation: 72271

As Oliv's answer explains, the language just doesn't allow partial specialization of a nested class.

In this case, you could also get your desired results by making the inner class a type alias, without the inheritance:

template <bool rgb>
struct colors_type;

template <>
struct colors_type<true> {
    enum : std::size_t { red, green, blue };
};

template <>
struct colors_type<false> {
    enum : std::size_t { cyan, magenta, yellow, black };
};

template <bool rgb, int i>
struct A {
  using colors = colors_type<rgb>;
  A();
};

There aren't many obvious benefits or drawbacks to these two solutions for this example, but possibly in some more complicated cases one or the other might be the easier or clearer path. (Though I would guess inheritance is a bit more often useful in these more general use cases.)

Upvotes: 1

Oliv
Oliv

Reputation: 18041

This is the way the language is specified:

Of interest in [temp.class.spec]:

A partial specialization of a class template provides an alternative definition of the template that is used instead of the primary definition

Each class template partial specialization is a distinct template and definitions shall be provided for the members of a template partial specialization

I think that the inheritance trick you provided is the best one trick, or at least the most used one.

Upvotes: 2

Related Questions