3
votes

In Where in the C++11 standard does it prohibit 'template <typename T> class A {...}; template <typename T> class A<int> {...};' (if anywhere)?, it's been confirmed that the following syntax is disallowed in the C++11 standard:

/* invalid C++ */

template <typename T>
class A
{
    public:
    T t;
};

// phony "full specialization" that mistakenly attempts
// to introduce a *new* template parameter
template <typename T>
class A<int>
{
    public:
    int i;
    T t;
};

With full understanding that the above syntax does not represent valid C++, I nonetheless could imagine a syntactically unambiguous use of the above code snippet, as follows:

A<float> a1;
A<int><double> a2;

a1.t = 2.0f;
a2.i = 2;
a2.t = 2.0;

It would seem syntactically and semantically unambiguous for C++ to support the syntax above.

(If the intended semantics is not clear to anybody, please post a comment and I will explain.)

I would describe this syntax as "introducing a new template parameter in a full specialization".

In this imagined scenario of a C++ compiler modified to support the above syntax and semantics, the compiler would see A<int> a2; and recognize that the attempted instantiation matches the primary template; it would then search for specializations and find and select the full specialization A<int> (ignoring the <double>); it would then note that this full specialization introduces a new template parameter T which in the case of the declared variable a2 is a double.

If I'm right that the above syntax and semantics is unambiguous, then I'd like to know why this is not part of C++. I can think of three reasons, but perhaps the answer is different or more complicated.

  • There are (almost) no real-world scenarios where this would be useful
  • It's too complex for current compilers to be asked to support
  • There are too many potential conflicts with other language features that would necessitate many new rules
  • Actually, a fourth possibility - the above syntax and semantics would be useful in some real-world scenarios and could be reasonably included in the standard, but it's just not part of the current standard.

I'd like to know why this isn't supported in C++ - am I correct that one of my bullet points provides the answer?

4
Using a special syntax like A<int><double> would thoroughly defeat the point of specialization. Just make a new class named B with two template parameters, and use it as B<int, double>. The whole point of specialization is that the client doesn't need to do anything special or even know it exists - it just uses the syntax of the primary template, and the specialization automatically kicks in as necessary.Igor Tandetnik
@IgorTandetnik I like that answer. Could you post it as an answer for consideration?Dan Nissenbaum
@IgorTandetnik - I could see one scenario where there would be a difference in behavior with the syntax in my question. If you leave the class name as A and use A<int><double> (rather than introducing a new class, B), then you could automatically disable (via compiler errors) all the A specializations of the form A<T1><T2> simply by commenting out the primary template for A. Also, by using A it would be clear that you mean the same thing semantically in some sense everywhere you use A.Dan Nissenbaum
I don't understand what you are saying. It doesn't make sense to have a specialization without a primary template - what exactly would the specialization be a special case of? If you comment out the primary template, and that somehow disables A<T1><T2> specializations in that hypothetical world of yours, then what's left? Is there something named A that still could somehow be used?Igor Tandetnik
You are not specializing a class template, you are specializing a (hypothetical) class template template. (Now imagine template template template parameters of class template templates.)L. F.

4 Answers

5
votes

Oooh, this explodes in funny ways.

For clarity, I assume you mean that with your example code,

 A<double> a; // okay
 A<int> a2;   // not okay, A<int> is not a class but a class template.

Now let's try to use that in other templates. Consider

template<typename T>
void function<A<T> const &a) { ... }

A<double> a;
A<int><double> a2;

function(a);  // that looks okay.
function(a2); // er...compiler error, I guess?

That's not too bad yet; the compiler could just complain about that. I think we're beginning to see how this is a bit strange, though. Well, take it up a notch:

template<template<typename> class A> struct Foo { ... };

Foo<A> f; // this would usually work, does it now?

If you answer no, what if the specialisation is not known at the point of compilation?

EDIT: Expanding this a bit, consider a real-world scenario for template-template arguments that I like to call pack traits:

template<template<typename> class, typename...> struct applies_to_all;

template<template<typename> class predicate, typename T, typename... Pack>
struct applies_to_all<predicate, T, Pack...> {
  static bool const value =
    predicate<T>::value && applies_to_all<predicate, Pack...>::value;
};

template<template<typename> class predicate>
struct applies_to_all<predicate> {
  static bool const value = true;
};

...

bool b = applies_to_all<std::is_integral, int, long, double>::value;

And feed this something like A that's a class for everything but int and a class template for int. Then try to work out

applies_to_all<A, double, float, std::string, int>::value

And note that this simple case is not the code you're likely to see. What you really get is this stuff nested three levels deep in other templates, where it looks like this:

applies_to_all<A, T...>::value

Possibly a behavior could be defined for this that is mathematically consistent, but I doubt that one could be defined that is useful. Certainly it's not POLA-compliant.

There's probably a lot more you could come up with along these lines. Blurring the lines between classes and class templates in this fashion is bound to break all sorts of stuff.

3
votes

A name in C++ represents one kind of entity, and this kind is known by the first name lookup.

The name A in your example is a class template name. Thus it follows that A<T> for any T is a class name.

This is a useful property for a language to have. Breaking this property means the language gets a major bump in complexity (for both compiler and user).

So even if you could find all the edge cases and fix the issues Wintermute has pointed out, it would still be most likely be rejected, because it makes the language more irregular, more surprising, less uniform. C++ is already burdened by quite a bit of irregularity; it doesn't make sense to add more.

I would encourage you to post your intended use case, and we can probably show you a better way.

1
votes

Template resolution is always against the primary template.

Any additional template parameters introduced in a specialization, would just be hanging in thin air, with nothing to connect them to any template usage.

1
votes

One of the core semantics of templates in C++ is that a template-id has a single template-argument-list: A<int>. Your proposed change allows a template-id to have more than one template-argument-list: A<int><double>.

You can achieve the desired result in standard C++ using template default-arguments:

template <typename T, typename U = void>
struct A
{
    T t;
};

template <typename U>
struct A<int, U>
{
    int i;
    T t;
};

A<float> a1;
A<int, double> a2;

a1.t = 2.0f;
a2.i = 2;
a2.t = 2.0;

The syntax A<int, double> is simpler than A<int><double> - a single argument list rather than multiple argument lists - which is the main reason why I wouldn't make your proposed change.

Having said that, it would be possible to relax the requirement that all explicit/partial specializations of a template take the same number of template arguments. Instead, it could be required that a specialization takes as least as many template arguments as the primary template. If that was the case, you could omit the redundant second parameter in the primary template.

template <typename T>
struct A; // primary template, definition omitted for brevity

template <typename U>
struct A<int, U>; // partial specialization, definition omitted for brevity

A<float> a1; // OK, selects primary template
A<int, double> a2; // OK, selects partial specialization
A<float, float> a3; // Error, no valid specialization

I don't think this change would break anything. The next question is whether it would improve anything?