What is the design reason template overloading of structs is not allowed in C++20?

Viewed 359

In answer to my previous question I learned that C++ 20 concepts do not allow overloading on struct template arguments, for example this does not work:

#include <concepts>

template <std::integral>
struct S{

};
template <std::floating_point>
struct S{
};

For curious clang error is something like, but that does not matter since I know C++ standard does not allow this code to work:

type constraint differs in template redeclaration

I find this nonworking way of writing templates very natural so I wonder was this ever considered, and if so why it was rejected during standardization?

P.S. this seems to work in C++20, but I find it much uglier

#include <concepts>
#include <iostream>

template <typename T>
requires std::integral<T> || std::floating_point<T>
struct S{

};
template <std::integral T>
struct S<T>{
    static constexpr char msg[] = "i";

};
template <std::floating_point T>
struct S<T>{
    static constexpr char msg[] = "fp";
};

int main() {
    std::cout <<  S<char>::msg << std::endl;
    std::cout <<  S<double>::msg << std::endl;
}
2 Answers

C++ has never had overloading for classes or class templates. Classes of course have no parameters with which an overload might be chosen, but neither can one write

template<class> struct A {};
template<int> struct A {};

even though for every template-id it is obvious which would pertain (A<int> vs. A<1>). There are several reasons for this restriction:

  1. It’s impossible to write generic code that uses one of the above overloads chosen at instantiation time: for every A<…>, whether the argument is a type or a value is fixed even if it’s dependent. (This wouldn’t be true if the overloads were template<int&> and template<float&>, of course.)
  2. Templates are occasionally mentioned without any template arguments, and there’s no syntax for selecting one where both might apply. One such context is as a template template argument (that might be of the generic template<class...> class variety); another is CTAD.
  3. Some ADL-like mechanisms would be necessary to support the case of overloading a class template for an application type after generic code that used the template(s).
  4. It would not in general be possible to determine to which overload a partial specialization pertained.

The C++20 behavior is just the continuation of this model; it’s not hard to add a generic

template<class> struct S;  // undefined

to serve as an umbrella over partial specializations declared as

template<std::integral I>
struct S<I> {};
template<std::floating_point F>
struct S<F> {};

Template argument deduction does not work with an explicit specialization of a class template (in comparison to a function template). This has nothing to do with concepts per se. You will need the template<> keyword and the <T> parameter.

I do not know of and cannot find any standard proposal further shortening this.

In the following code especially the primary definition of S got much shorter than the working version in the question.

#include <concepts>

template <typename T>
concept Number = std::integral<T> || std::floating_point<T>;


template <Number T>
struct S;

template <std::integral T>
struct S<T>{
};

template <std::floating_point T>
struct S<T>{
};

The above code compiles and works.

By using Number the first line of the error message for instantiating S with e.g. std::string is:

error: template constraint failure for 'template<class T> requires Number<T> struct S'

Which is easy to understand and to the point (with more detailing information in the following lines about std::string not being an integral || floating_point). So it is possible to build levels of error messages for wrong instantiations (e.g. you could define your own integral concept which lists the allowed integer types).

Related