Using unconstrained template template parameters with constrained templates (original) (raw)

1. The problem

It is our understanding that any template accepting an unconstrained template template parameter cannot accept a constrained template as pointed out in Concepts TS Issue 14. This creates problems with both legacy and new code.

To illustrate the problem, define the following classes

template<class T, class U> struct Unconstrained { Unconstrained(U, V);};

template<class T, class U, class = enable_if_t<is_integral_v>> struct Sfinae { Sfinae(U, V); };

template<class T, class U> requires Integral struct Conceptized { Conceptized(U, V); };

In Concepts TS issue 14, the following was a blocker to adding concepts to sqlpp11.

// A template that takes a tuple and copies its arguments into another template (sink) template<typename Tuple, template<typename...> class Sink> using copy_tuple_args = ...

copy_tuple_args<tuple<char *, int>, Unconstrained> u1; // OK. Unconstrained<char *, int> copy_tuple_args<tuple<char *, int>, Sfinae> s1; // OK. Sfinae<char *, int> copy_tuple_args<tuple<char *, int>, Conceptized> c1; // Ill-formed instead of desired Conceptized<char *, int> :(

As C++17 template template parameters are incompatible with concept-constrained templates, any attempt to concept constrain templates in existing code bases that make use of template template parameters will be likely to cause difficult or even impossible to fix breakage, discouraging the adoption of Concepts:

// Valid C++17 template template<template<class, class> class TT, class A> struct curry {
template using type = TT<A, B>; };

curry<Unconstrained, char *>::type u3; // OK. Unconstrained<char *, int> curry<Sfinae, char *>::type s3 // OK. Sfinae<char *, int> curry<Conceptized, char *>::type c3; // Ill-formed instead of desired Conceptized<char *, int>

There is not even a way to write new code with template template parameters without introducing restrictions on constraints, hindering use cases like Inferencing heap objects (P1069):

// Notation of P1069 auto u4 = make_unique(3, 5); // OK. unique_ptr<Unconstrained<int, int>> auto s4 = make_unique(3, 5); // OK. unique_ptr<Unconstrained<int, int>> auto c4 = make_unique(3, 5); // Ill-formed instead of desired Conceptized<int, int>

Likewise, ranges::to (P1206) awkwardly relies on standard library containers being unconstrained:

// copy a list to a vector of the same type, deducing value_type Same<std::vector> auto c = ranges::tostd::vector(l); // OK, vector is not concept-constrained

2. The solution

We propose simply that an unconstrained template template parameter match concept-constrained classes. E.g.,

copy_tuple_args<tuple<char *, int>, Conceptized> c1; // Proposed: Conceptized<char *, int> curry<Conceptized, char *>::type c3; // Proposed: Conceptized<char *, int> auto c4 = make_unique(3, 5); // Proposed: Conceptized<int, int>

Note that violations of the constraints will still produce concept-aware error messages

// Proposed: Ill-formed with concept violation as desired copy_tuple_args<tuple<int, char *>, Conceptized> c5;

3. Without a language change, can we accept a general template as a template template parameter?

As far as we can tell, you can’t. So use cases like the above are simply impossible. (Did we miss something? We’d love to be proven wrong).

4. Why not create a new notation that matches any template?

While one could imagine some new notation for template template parameters that works with and without constraints, we believe that would still be an unsatisfactory solution for the following reasons

5. What if we want to only match unconstrained templates?

We are unaware of use cases for the current behavior of only matching unconstrained concepts (especially compared to the significant use cases for this proposal). However, note that natural notations could easily be developed and added, e.g., by making the lack of constraints explicit:

template<template<class, class> requires {} class X> // Not proposed at this time class OnlyMatchesUnconstrained {};

OnlyMatchesUnconstrained u5; // OK OnlyMatchesUnconstrained s5; // OK OnlyMatchesUnconstrained s5; // Ill-formed as desired

6. Wording

Change the beginning of temp.arg.template/3 to say

A template-argument matches a template template-parameter P when P is at least as specialized as the template-argument A. In this comparison, if P is unconstrained, the constraints on A are not considered. If P contains...