4

I have encountered a situation where my class template partial specialisations share a lot of code and it makes sense to move that into a base class. However it does not make sense for all of the specialisations to have the same base class.

The following example code compiles in GCC 7.1 without error:

struct foo_base_1 { void bar() { std::cout << "base 1" << std::endl; }; };

struct foo_base_2 { void bar() { std::cout << "base 2" << std::endl; }; };

template <typename A, typename B>
struct foo { };

template <typename A>
struct foo<A, int> : foo_base_1 { };

template <typename A>
struct foo<A, double> : foo_base_2 { };

int main()
{
    foo<int, int> x;
    foo<int, double> y;

    x.bar();
    y.bar();
}

I realise that despite being specialisations of the same class they are effectively different types. Still, it feels wrong that the same class could inherit from different bases.

What I would like is some reassurance that this is OK. I can't find the relevant part of the Standard and I'm not willing to trust it just because it compiles (I've been bitten before).

Fibbs
  • 1,350
  • 1
  • 13
  • 23
  • 3
    This pattern is used all over the standard library for type traits. Generally the base template will derive from `std::false_type` and have some number of specializations that derive from `std::true_type`. – Miles Budnek Jul 24 '18 at 17:29
  • Consider that `foo` is simply `foo{}` with no base class whatsoever :) –  Jul 24 '18 at 17:31
  • @MilesBudnek I hadn't considered that but it's a great practical example. – Fibbs Jul 24 '18 at 17:48

1 Answers1

5

Each template instantiation is a separate class of its own. Just as any ordinary class can inherit from whichever base classes you desire, template instantiations can do so as well. Consider the following example:

template <typename Base>
class Derived : public Base
{
};

Absolutely legal...

This is, for instance, the base of the curiously recurring template pattern.

It does not matter if you now decide to implement a specific instantiation differently (so you specialise; or group of, then partially) than the original/main template pattern.

The standard seems to be a little short about the topic:

17.5.1 Class templates [temp.class]
1 A class template defines the layout and operations for an unbounded set of related types.
2 [Example: A single class template List might provide an unbounded set of class definitions: one class List for every type T, each describing a linked list of elements of type T. Similarly, a class template Array describing a contiguous, dynamic array might be defined like this: [ some sample template declaration ] The prefix template specifies that a template is being declared and that a type-name T may be used in the declaration. In other words, Array is a parameterized type with T as its parameter. — end example]

[highlighting by me], but uses the same pattern later on when defining type traits:

23.15.3 Helper classes [meta.help]
[definition of integral_constant]
1 The class template integral_constant, alias template bool_constant, and its associated typedef-names true_type and false_type are used as base classes to define the interface for various type traits.

Subsequently, the standard uses the wording "having a base characteristic of", as in:

23.15.4 Unary type traits [meta.unary]
1 This subclause contains templates that may be used to query the properties of a type at compile time.
2 Each of these templates shall be a UnaryTypeTrait (23.15.1) with a base characteristic of true_type if the corresponding condition is true, otherwise false_type.

Together with the previous citation, we can conclude that it is at least legal to inherit from either true_type or false_type selectively, according to the condition being fulfilled or not. Difficult to say if this wording even enforces this inheritance or if it is legal as well to define the templates such that they only behave like true_type and false_type without explicitly inheriting from (which is another matter, though...).

Not authoritative, of course, I personally tend to the former interpretation due to the preceding "are used as" (in contrast to "can be used as").

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • I am happy enough now that this is legal based on a combination of everyone's explanations. However I'm reluctant to accept the answer as is. Could you expand on "Absolutely legal" since that answer is basically asking people to take your word for it? Maybe something about how the standard library uses `std::true_type` with regard to type traits? – Fibbs Jul 24 '18 at 17:53
  • Thanks for expanding it, hopefully it'll help future googlers. – Fibbs Jul 24 '18 at 18:51
  • Actually, `class Derived : public Base {};` would be the CRTP. – Deduplicator Jul 24 '18 at 19:51
  • @Deduplicator I just said "it's the base for", not "it *is*". CRTP is explained in the link provided... Intention was to provide a use case. – Aconcagua Jul 24 '18 at 20:00